This commit is contained in:
史典卓
2025-03-28 15:19:42 +08:00
parent ad4eb162a5
commit 0216f6053a
396 changed files with 18278 additions and 9899 deletions

BIN
stores/.DS_Store vendored Normal file

Binary file not shown.

64
stores/BaseDBStore.js Normal file
View File

@@ -0,0 +1,64 @@
// BaseStore.js - 基础Store类
import IndexedDBHelper from '@/common/IndexedDBHelper.js'
import useChatGroupDBStore from './userChatGroupStore'
import config from '@/config'
class BaseStore {
db = null
isDBReady = false
dbName = 'BrowsingHistory'
constructor() {
this.checkAndInitDB()
}
checkAndInitDB() {
// 获取本地数据库版本
const localVersion = uni.getStorageSync('indexedDBVersion') || 1
console.log('DBVersion: ', localVersion, config.DBversion)
if (localVersion === config.DBversion) {
this.initDB()
} else {
console.log('清空本地数据库')
this.clearDB().then(() => {
uni.setStorageSync('indexedDBVersion', config.DBversion);
this.initDB();
});
}
}
initDB() {
this.db = new IndexedDBHelper(this.dbName, config.DBversion);
this.db.openDB([{
name: 'record',
keyPath: "id",
autoIncrement: true,
}, {
name: 'messageGroup',
keyPath: "id",
autoIncrement: true,
}, {
name: 'messages',
keyPath: "id",
autoIncrement: true,
indexes: [{
name: 'parentGroupId',
key: 'parentGroupId',
unique: false
}]
}]).then(async () => {
useChatGroupDBStore().init()
this.isDBReady = true
});
}
async clearDB() {
return new Promise((resolve, rejetc) => {
new IndexedDBHelper().deleteDB(this.dbName).then(() => {
resolve()
})
})
}
}
const baseDB = new BaseStore()
export default baseDB

181
stores/useDictStore.js Normal file
View File

@@ -0,0 +1,181 @@
import {
defineStore
} from 'pinia';
import {
reactive,
ref,
} from 'vue'
import {
createRequest
} from "../utils/request";
// 静态树 O(1) 超快查询!!!!!
let IndustryMap = null
// 构建索引
function buildIndex(tree) {
const map = new Map();
function traverse(nodes) {
for (const node of nodes) {
map.set(node.id, node);
if (node.children && node.children.length) {
traverse(node.children);
}
}
}
traverse(tree);
return map;
}
const useDictStore = defineStore("dict", () => {
// 定义状态
const complete = ref(false)
const state = reactive({
education: [],
experience: [],
area: [],
scale: [],
isPublish: [],
sex: [],
affiliation: [],
industry: []
})
// political_affiliation
const getDictData = async (dictType, dictName) => {
try {
if (dictType && dictName) {
return getDictSelectOption(dictType).then((data) => {
state[dictName] = data
return data
})
}
const [education, experience, area, scale, sex, affiliation] = await Promise.all([
getDictSelectOption('education'),
getDictSelectOption('experience'),
getDictSelectOption('area', true),
getDictSelectOption('scale'),
getDictSelectOption('app_sex'),
getDictSelectOption('political_affiliation'),
]);
state.education = education;
state.experience = experience;
state.area = area;
state.scale = scale;
state.sex = sex;
state.affiliation = affiliation;
complete.value = true
getIndustryDict() // 获取行业
} catch (error) {
console.error('Error fetching dictionary data:', error);
}
};
async function getIndustryDict() {
if (state.industry.length) return
const resp = await createRequest(`/app/common/industry/treeselect`);
if (resp.code === 200 && resp.data) {
state.industry = resp.data
IndustryMap = buildIndex(resp.data);
}
return [];
}
function industryLabel(dictType, value) {
switch (dictType) {
case 'industry':
if (!IndustryMap) return
const data = IndustryMap.get(Number(value))?.label || ''
return data
}
return null
}
function dictLabel(dictType, value) {
if (state[dictType]) {
for (let i = 0; i < state[dictType].length; i++) {
let element = state[dictType][i];
if (element.value === value) {
return element.label
}
}
}
return ''
}
function oneDictData(dictType, value) {
if (!value) {
return state[dictType]
}
if (state[dictType]) {
for (let i = 0; i < state[dictType].length; i++) {
let element = state[dictType][i];
if (element.value === value) {
return element
}
}
}
return null
}
function getTransformChildren(dictType, title = '', key = '') {
if (dictType) {
return {
label: title,
key: key || dictType,
options: state[dictType],
}
}
return null
}
async function getDictSelectOption(dictType, isDigital) {
const resp = await createRequest(`/app/common/dict/${dictType}`);
if (resp.code === 200 && resp.data) {
const options = resp.data.map((item) => {
return {
text: item.dictLabel,
label: item.dictLabel,
value: isDigital ? Number(item.dictValue) : item.dictValue,
key: item.dictCode,
listClass: item.listClass,
status: item.listClass,
};
});
return options;
}
return [];
}
async function getDictValueEnum(dictType, isDigital) {
const resp = await createRequest(`/app/common/dict/${dictType}`);
if (resp.code === 200 && resp.data) {
const opts = {};
resp.data.forEach((item) => {
opts[item.dictValue] = {
text: item.dictLabel,
label: item.dictLabel,
value: isDigital ? Number(item.dictValue) : item.dictValue,
key: item.dictCode,
listClass: item.listClass,
status: item.listClass,
};
});
return opts;
} else {
return {};
}
}
// 导入
return {
getDictData,
dictLabel,
oneDictData,
complete,
getDictSelectOption,
getTransformChildren,
industryLabel
}
})
export default useDictStore;

View File

@@ -0,0 +1,69 @@
import {
defineStore
} from 'pinia';
import {
ref
} from 'vue'
import {
msg
} from '@/common/globalFunction.js'
const useLocationStore = defineStore("location", () => {
// 定义状态
const longitudeVal = ref('') // 经度
const latitudeVal = ref('') //纬度
function getLocation() {
return new Promise((resole, reject) => {
uni.getLocation({
type: 'wgs84',
altitude: true,
isHighAccuracy: true,
enableHighAccuracy: true, // 关键参数:启用传感器辅助
timeout: 10000,
success: function(res) {
const resd = {
longitude: 120.382665,
latitude: 36.066938
}
longitudeVal.value = resd.longitude
latitudeVal.value = resd.latitude
msg('用户位置获取成功')
resole(resd)
},
fail: function(err) {
// longitudeVal.value = ''
// latitudeVal.value = ''
// reject(err)
const resd = {
longitude: 120.382665,
latitude: 36.066938
}
longitudeVal.value = resd.longitude
latitudeVal.value = resd.latitude
msg('用户位置获取失败,使用模拟定位')
resole(resd)
},
complete: function(e) {
console.warn('getUserLocation' + JSON.stringify(e))
}
})
})
}
function longitude() {
return longitudeVal.value
}
function latitude() {
return latitudeVal.value
}
// 导入
return {
getLocation,
longitude,
latitude,
}
})
export default useLocationStore;

View File

@@ -0,0 +1,113 @@
import {
defineStore
} from 'pinia';
import {
ref
} from 'vue'
import IndexedDBHelper from '@/common/IndexedDBHelper.js'
import useDictStore from '@/stores/useDictStore';
import jobAnalyzer from '@/utils/jobAnalyzer';
import {
msg
} from '@/common/globalFunction.js'
import baseDB from './BaseDBStore';
class JobRecommendation {
constructor() {
this.conditions = {}; // 存储最新的条件及其出现次数
this.askHistory = new Map(); // 记录每个条件的最后询问时间
this.cooldown = 5 * 60 * 1000; // 冷却时间(单位:毫秒)
}
updateConditions(newConditions) {
this.conditions = newConditions;
}
getCurrentTime() {
return Date.now();
}
/**
* 获取下一个符合条件的推荐问题
* @returns {string|null} 返回推荐的问题,或 null无可询问的
*/
getNextQuestion() {
const now = this.getCurrentTime();
// 按照出现次数降序排序
const sortedConditions = Object.entries(this.conditions)
.sort((a, b) => b[1] - a[1]); // 按出现次数降序排序
for (const [condition, count] of sortedConditions) {
const lastAskedTime = this.askHistory.get(condition);
if (!lastAskedTime || now - lastAskedTime >= this.cooldown) {
this.askHistory.set(condition, now);
return condition;
}
}
return null; // 没有可询问的
}
}
// **🔹 创建推荐系统**
export const jobRecommender = new JobRecommendation();
export const useRecommedIndexedDBStore = defineStore("indexedDB", () => {
const tableName = ref('record')
const total = ref(200) // 记录多少条数据
// 插入数据
async function addRecord(payload) {
const totalRecords = await baseDB.db.getRecordCount(tableName.value);
if (totalRecords >= total.value) {
console.log(`⚠数据超过 ${total.value} 条,删除最早的一条...`);
await baseDB.db.deleteOldestRecord(tableName.value);
}
if (!baseDB.isDBReady) await baseDB.initDB();
return await baseDB.db.add(tableName.value, payload);
}
// 获取所有数据
async function getRecord() {
if (!baseDB.isDBReady) await baseDB.initDB();
return await baseDB.db.getAll(tableName.value);
}
// 格式化浏览数据岗位数据
function JobParameter(job) {
const experIenceLabel = useDictStore().dictLabel('experience', job.experience)
const jobLocationAreaCodeLabel = useDictStore().dictLabel('area', job.jobLocationAreaCode)
return {
jobCategory: job.jobCategory,
jobTitle: job.jobTitle,
minSalary: job.minSalary,
maxSalary: job.maxSalary,
experience: job.experience,
experIenceLabel,
jobLocationAreaCode: job.jobLocationAreaCode,
jobLocationAreaCodeLabel,
createTime: Date.now()
}
}
function analyzer(jobsData) {
const result = jobAnalyzer.analyze(jobsData)
const sort = jobAnalyzer.printUnifiedResults(result)
return {
result,
sort
}
}
return {
addRecord,
getRecord,
JobParameter,
analyzer
};
});

View File

@@ -4,13 +4,51 @@ import {
import {
ref
} from 'vue'
import {
createRequest
} from '../utils/request';
import similarityJobs from '@/utils/similarity_Job.js';
import {
UUID
} from "@/lib/uuid-min.js";
// 简历完成度计算
function getResumeCompletionPercentage(resume) {
const requiredFields = [
'name',
'age',
'sex',
'birthDate',
'education',
'politicalAffiliation',
'phone',
'salaryMin',
'salaryMax',
'area',
'status',
'jobTitleId',
'jobTitle',
];
const totalFields = requiredFields.length;
let filledFields = requiredFields.filter((field) => {
const value = resume[field];
return value !== null && value !== '' && !(Array.isArray(value) && value.length === 0);
}).length;
return ((filledFields / totalFields) * 100).toFixed(0) + '%';
}
const useUserStore = defineStore("user", () => {
// 定义状态
const hasLogin = ref(false)
const openId = ref('')
// const openId = ref('')
const userInfo = ref({});
const token = ref('测试token')
const role = ref({});
const token = ref('')
const resume = ref({})
const Completion = ref('0%')
const seesionId = ref(uni.getStorageSync('seesionId') || '')
const login = (value) => {
hasLogin.value = true;
@@ -24,17 +62,79 @@ const useUserStore = defineStore("user", () => {
}
const logOut = () => {
hasLogin = false;
hasLogin.value = false;
token.value = ''
resume.value = {}
userInfo.value = {}
role.value = {}
uni.clearStorageSync('userInfo')
uni.clearStorageSync('token')
uni.redirectTo({
url: '/pages/login/login',
});
}
const getUserInfo = () => {
return new Promise((reslove, reject) => {
createRequest('/getInfo', {}, 'get').then((userInfo) => {
setUserInfo(userInfo);
reslove(userInfo)
});
})
}
const getUserResume = () => {
return new Promise((reslove, reject) => {
createRequest('/app/user/resume', {}, 'get').then((resume) => {
Completion.value = getResumeCompletionPercentage(resume.data)
similarityJobs.setUserInfo(resume.data)
setUserInfo(resume);
reslove(resume)
});
})
}
const loginSetToken = async (value) => {
token.value = value
uni.setStorageSync('token', value);
// 获取用户信息
return getUserResume()
}
const setUserInfo = (values) => {
userInfo.value = values.data;
// role.value = values.role;
hasLogin.value = true;
}
const tokenlogin = (token) => {
createRequest('/app/login', {
token
}).then((resData) => {
onsole.log(resData)
})
}
const initSeesionId = () => {
const seesionIdVal = UUID.generate()
uni.setStorageSync('seesionId', seesionIdVal);
seesionId.value = seesionIdVal
}
// 导入
return {
hasLogin,
openId,
userInfo,
token,
resume,
login,
logOut
logOut,
loginSetToken,
getUserResume,
initSeesionId,
seesionId,
Completion
}
})

View File

@@ -0,0 +1,291 @@
import {
defineStore
} from 'pinia';
import {
reactive,
ref
} from 'vue'
import IndexedDBHelper from '@/common/IndexedDBHelper.js'
import baseDB from './BaseDBStore';
import {
msg,
CloneDeep,
$api,
formatDate,
insertSortData
} from '../common/globalFunction';
import {
UUID
} from '../lib/uuid-min';
import config from '../config';
const useChatGroupDBStore = defineStore("messageGroup", () => {
const tableName = ref('messageGroup')
const massageName = ref('messages')
const messages = ref([]) // 消息列表
const isTyping = ref(false) // 是否正在输入
const textInput = ref('')
// tabel
const tabeList = ref([])
const chatSessionID = ref('')
const lastDateRef = ref('')
// const groupPages = reactive({
// page: 0,
// total: 0,
// maxPage: 2,
// pageSize: 50,
// })
async function init() {
// 获取所有数据
setTimeout(async () => {
if (!baseDB.isDBReady) await baseDB.initDB();
const result = await baseDB.db.getAll(tableName.value);
// 1、判断是否有数据没数据请求服务器
if (result.length) {
console.warn('本地数据库存在数据')
const list = result.reverse()
const [table, lastData] = insertSortData(list)
tabeList.value = table
const tabelRow = list[0]
chatSessionID.value = tabelRow.sessionId
initMessage(tabelRow.sessionId)
} else {
console.warn('本地数据库存在数据')
getHistory('refresh')
}
}, 1000)
}
async function initMessage(sessionId) {
if (!baseDB.isDBReady) await baseDB.initDB();
chatSessionID.value = sessionId
const list = await baseDB.db.queryByField(massageName.value, 'parentGroupId', sessionId);
if (list.length) {
console.log('本地数据库存在该对话数据', list)
messages.value = list
} else {
console.log('本地数据库不存在该对话数据')
getHistoryRecord('refresh')
}
}
async function addMessage(payload) {
if (!chatSessionID.value) {
return msg('请创建对话')
}
const params = {
...payload,
parentGroupId: chatSessionID.value,
files: payload.files || [],
}
messages.value.push(params);
addMessageIndexdb(params)
}
function toggleTyping(status) {
isTyping.value = status
}
async function addTabel(text) {
if (!baseDB.isDBReady) await baseDB.initDB();
const uuid = UUID.generate() // 对话sessionId
let payload = {
title: text,
createTime: formatDate(Date.now()),
sessionId: uuid
}
const id = await baseDB.db.add(tableName.value, payload);
const list = await baseDB.db.getAll(tableName.value);
chatSessionID.value = uuid
const [result, lastData] = insertSortData(list)
tabeList.value = result
return id
}
async function addMessageIndexdb(payload) {
console.log(payload)
return await baseDB.db.add(massageName.value, payload);
}
async function getStearm(text, fileUrls = [], progress) {
return new Promise((resolve, reject) => {
try {
toggleTyping(true);
const params = {
data: text,
sessionId: chatSessionID.value,
};
if (fileUrls && fileUrls.length) {
params['fileUrl'] = fileUrls.map((item) => item.url)
}
const newMsg = {
text: '', // 存放完整的流式数据
self: false,
displayText: '' // 用于前端逐步显示
};
const index = messages.value.length;
messages.value.push(newMsg); // 先占位
let fullText = ''; // 存储完整的响应内容
function handleUnload() {
newMsg.text = fullText
newMsg.parentGroupId = chatSessionID.value
baseDB.db.add(massageName.value, newMsg);
}
// 添加事件监听
window.addEventListener("unload", handleUnload);
// 实时数据渲染
function onDataReceived(data) {
// const parsedData = safeParseJSON(data);
fullText += data; // 累积完整内容
newMsg.displayText += data; // 逐步更新 UI
messages.value[index] = {
...newMsg
}; // 触发视图更新
progress && progress()
}
// 异常处理
function onError(error) {
console.error('请求异常:', error);
msg('服务响应异常')
reject(error);
}
// 完成处理
function onComplete() {
newMsg.text = fullText; // 保存完整响应
messages.value[index] = {
...newMsg
};
toggleTyping(false);
window.removeEventListener("unload", handleUnload);
handleUnload()
resolve && resolve();
}
$api.streamRequest('/chat', params, onDataReceived,
onError, onComplete)
} catch (err) {
console.log(err);
reject(err);
}
})
}
// 状态控制
function addNewDialogue() {
chatSessionID.value = ''
messages.value = []
}
function changeDialogue(item) {
chatSessionID.value = item.sessionId
initMessage(item.sessionId)
}
// 云端数据
function getHistory() {
$api.chatRequest('/getHistory').then((res) => {
if (!res.data.list.length) return
let tabel = parseHistory(res.data.list)
if (tabel && tabel.length) {
const tabelRow = tabel[0] // 默认第一个
const [result, lastData] = insertSortData(tabel)
// console.log('getHistory insertSortData', result, lastData)
chatSessionID.value = tabelRow.sessionId
tabeList.value = result
getHistoryRecord(false)
baseDB.db.add(tableName.value, tabel);
lastDateRef.value = lastData
}
})
}
function getHistoryRecord(loading = true) {
const params = {
sessionId: chatSessionID.value
}
$api.chatRequest('/detail', params, 'GET', loading).then((res) => {
console.log('detail', res.data)
let list = parseHistoryDetail(res.data.list, chatSessionID.value)
if (list.length) {
messages.value = list
baseDB.db.add(massageName.value, list);
}
console.log('解析后:', list)
}).catch(() => {
msg('请求出现异常,请联系工作人员')
})
}
// 解析器
function parseHistory(list) {
return list.map((item) => ({
title: item.title,
createTime: item.updateTime,
sessionId: item.chatId
}))
}
function parseHistoryDetail(list, sessionId) {
const arr = []
for (let i = 0; i < list.length; i++) {
const element = list[i];
const self = element.obj !== 'AI'
let text = ''
let files = []
for (let j = 0; j < element.value.length; j++) {
const obj = element.value[j];
if (obj.type === 'text') {
text += obj.text.content
}
if (obj.type === 'file') {
files.push(obj.file)
}
}
arr.push({
parentGroupId: sessionId,
displayText: text,
self: self,
text: text,
dataId: element.dataId,
files,
})
}
return arr
}
return {
messages,
isTyping,
textInput,
chatSessionID,
addMessage,
tabeList,
init,
initMessage,
toggleTyping,
addTabel,
addNewDialogue,
changeDialogue,
getStearm,
getHistory,
};
});
function safeParseJSON(data) {
try {
return JSON.parse(data);
} catch {
return null; // 解析失败,返回 null
}
}
export default useChatGroupDBStore