feat: 开关房功能开发

This commit is contained in:
duanshuwen
2026-03-09 07:39:14 +08:00
parent 03b85cffb0
commit 15a7d115cb
13 changed files with 119 additions and 123 deletions

View File

@@ -57,6 +57,13 @@ const config: ForgeConfig = {
}), }),
], ],
hooks: { hooks: {
async packageAfterCopy(forgeConfig, buildPath, electronVersion, platform, arch) {
const src = path.join(__dirname, 'src/main/scripts');
const dest = path.join(buildPath, 'resources', 'scripts');
await fs.ensureDir(dest);
await fs.copy(src, dest);
},
async prePackage() { async prePackage() {
const outDir = path.resolve(process.cwd(), 'out'); const outDir = path.resolve(process.cwd(), 'out');
fs.rmSync(outDir, { recursive: true, force: true }); fs.rmSync(outDir, { recursive: true, force: true });

7
package-lock.json generated
View File

@@ -21,7 +21,6 @@
"crypto": "^1.0.1", "crypto": "^1.0.1",
"crypto-js": "^4.2.0", "crypto-js": "^4.2.0",
"dexie": "^4.2.1", "dexie": "^4.2.1",
"dotenv": "^17.2.3",
"dotenv-cli": "^11.0.0", "dotenv-cli": "^11.0.0",
"electron-log": "^5.4.3", "electron-log": "^5.4.3",
"electron-squirrel-startup": "^1.0.1", "electron-squirrel-startup": "^1.0.1",
@@ -4957,9 +4956,9 @@
} }
}, },
"node_modules/dotenv": { "node_modules/dotenv": {
"version": "17.2.3", "version": "17.3.1",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.3.1.tgz",
"integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==", "integrity": "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==",
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"engines": { "engines": {
"node": ">=12" "node": ">=12"

View File

@@ -58,7 +58,6 @@
"crypto": "^1.0.1", "crypto": "^1.0.1",
"crypto-js": "^4.2.0", "crypto-js": "^4.2.0",
"dexie": "^4.2.1", "dexie": "^4.2.1",
"dotenv": "^17.2.3",
"dotenv-cli": "^11.0.0", "dotenv-cli": "^11.0.0",
"electron-log": "^5.4.3", "electron-log": "^5.4.3",
"electron-squirrel-startup": "^1.0.1", "electron-squirrel-startup": "^1.0.1",

View File

@@ -1,9 +1,10 @@
import { ipcMain } from 'electron'; import { ipcMain, app } from 'electron';
import { IPC_EVENTS } from '@common/constants'; import { IPC_EVENTS } from '@common/constants';
import { launchLocalChrome } from '@main/utils/chrome/launchLocalChrome' import { launchLocalChrome } from '@main/utils/chrome/launchLocalChrome'
import { executeScriptService } from '@main/service/execute-script-service'; import { executeScriptService } from '@main/service/execute-script-service';
import path from 'path' import path from 'path'
import log from 'electron-log';
export function runTaskOperationService() { export function runTaskOperationService() {
const executeScriptServiceInstance = new executeScriptService(); const executeScriptServiceInstance = new executeScriptService();
@@ -19,7 +20,17 @@ export function runTaskOperationService() {
*/ */
await launchLocalChrome(options) await launchLocalChrome(options)
const result = await executeScriptServiceInstance.executeScript(path.join(__dirname, '../../scripts/fg_trace.js'), options)
// 脚本路径处理
let scriptPath = ''
if (app.isPackaged) {
scriptPath = path.join(process.resourcesPath, 'scripts/fg_trace.js')
} else {
scriptPath = path.join(process.cwd(), 'src/main/scripts/fg_trace.js')
}
log.info(`Launching script with path: ${scriptPath}`);
const result = await executeScriptServiceInstance.executeScript(scriptPath, options)
return { success: true, result }; return { success: true, result };
} catch (error: any) { } catch (error: any) {

View File

@@ -0,0 +1,2 @@
import log from 'electron-log';

View File

@@ -1,9 +1,36 @@
import { chromium } from 'playwright'; import { chromium } from 'playwright';
import { checkLoginStatus } from '@utils/checkLoginStatus';
import dotenv from 'dotenv';
import log from 'electron-log'; import log from 'electron-log';
dotenv.config();
const checkLoginStatus = async (page) => {
try {
const currentUrl = await page.url();
log.info('current url==========>:', currentUrl);
const loginPagePatterns = ['/login', '/signin', '/auth'];
const isLoginPage = loginPagePatterns.some(pattern => currentUrl.includes(pattern));
if(!isLoginPage) {
return true;
}
// 检查页面内容中的关键字
const pageContent = await page.content();
const loginKeywords = ['退出', '房价房量管理'];
const hasLoginKeyword = loginKeywords.some(keyword =>
pageContent.includes(keyword)
);
if (hasLoginKeyword) {
return true;
}
return false;
} catch (error) {
return false;
}
}
(async () => { (async () => {
const browser = await chromium.connectOverCDP('http://localhost:9222'); const browser = await chromium.connectOverCDP('http://localhost:9222');
@@ -24,13 +51,13 @@ dotenv.config();
if(!isLogin) { if(!isLogin) {
await page.getByRole('textbox', { name: '请输入账号' }).dblclick(); await page.getByRole('textbox', { name: '请输入账号' }).dblclick();
await page.getByRole('textbox', { name: '请输入账号' }).click(); await page.getByRole('textbox', { name: '请输入账号' }).click();
await page.getByRole('textbox', { name: '请输入账号' }).fill(process.env.FZ_USERNAME, { delay: 80 + Math.random() * 120 }); await page.getByRole('textbox', { name: '请输入账号' }).fill('hoteltmwq123', { delay: 80 + Math.random() * 120 });
await page.waitForTimeout(1000 + Math.random() * 1000); await page.waitForTimeout(1000 + Math.random() * 1000);
await page.getByRole('button', { name: '下一步' }).click(); await page.getByRole('button', { name: '下一步' }).click();
const frame_1 = await page.frameLocator('#alibaba-login-box'); const frame_1 = await page.frameLocator('#alibaba-login-box');
await page.locator('#alibaba-login-box').contentFrame().getByRole('textbox', { name: '请输入登录密码' }).dblclick(); await page.locator('#alibaba-login-box').contentFrame().getByRole('textbox', { name: '请输入登录密码' }).dblclick();
await page.locator('#alibaba-login-box').contentFrame().getByRole('textbox', { name: '请输入登录密码' }).fill(process.env.FZ_PASSWORD, { delay: 80 + Math.random() * 120 }); await page.locator('#alibaba-login-box').contentFrame().getByRole('textbox', { name: '请输入登录密码' }).fill('Tmwq654321', { delay: 80 + Math.random() * 120 });
await page.waitForTimeout(1000 + Math.random() * 1000); await page.waitForTimeout(1000 + Math.random() * 1000);
await page.locator('#alibaba-login-box').contentFrame().getByRole('button', { name: '登录' }).click(); await page.locator('#alibaba-login-box').contentFrame().getByRole('button', { name: '登录' }).click();

View File

@@ -1,31 +0,0 @@
import log from 'electron-log';
export const checkLoginStatus = async (page: any) => {
try {
const currentUrl = await page.url();
log.info('current url==========>:', currentUrl);
const loginPagePatterns = ['/login', '/signin', '/auth'];
const isLoginPage = loginPagePatterns.some(pattern => currentUrl.includes(pattern));
if(!isLoginPage) {
return true;
}
// 检查页面内容中的关键字
const pageContent = await page.content();
const loginKeywords = ['退出', '房价房量管理'];
const hasLoginKeyword = loginKeywords.some(keyword =>
pageContent.includes(keyword)
);
if (hasLoginKeyword) {
return true;
}
return false;
} catch (error) {
return false;
}
}

View File

@@ -11,7 +11,7 @@ export async function launchLocalChrome (options: any) {
// 多账号隔离 // 多账号隔离
// const profileDir = getProfileDir(accountId); // const profileDir = getProfileDir(accountId);
log.info(`Launching Chrome with user data dir: ${options}`); // log.info(`Launching Chrome with user data dir: ${options}`);
// 检查端口是否被占用 // 检查端口是否被占用
const portInUse = await isPortInUse(9222); const portInUse = await isPortInUse(9222);

View File

@@ -4,15 +4,15 @@ import request from '@utils/request';
import * as API from './types'; import * as API from './types';
/** 新增房型映射 新增房型映射新增房型映射 POST /hotelStaff/typeMapping/add */ /** 查询房型映射列表 查询房型映射列表查询房型映射列表 POST /hotelStaff/typeMapping/list */
export function hotelStaffTypeMappingAddUsingPost({ export function hotelStaffTypeMappingListUsingPost({
body, body,
options, options,
}: { }: {
body: API.RoomTypeMapping; body: API.RoomTypeMapping;
options?: { [key: string]: unknown }; options?: { [key: string]: unknown };
}) { }) {
return request<API.RBoolean>('/hotelStaff/typeMapping/add', { return request<API.RListRoomTypeMapping>('/hotelStaff/typeMapping/list', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
@@ -22,25 +22,7 @@ export function hotelStaffTypeMappingAddUsingPost({
}); });
} }
/** 获取房型映射详细信息 获取房型映射详细信息获取房型映射详细信息 POST /hotelStaff/typeMapping/getInfo */ /** 分页查询房型映射列表 查询房型映射列表查询房型映射列表 POST /hotelStaff/typeMapping/pageList */
export function hotelStaffTypeMappingGetInfoUsingPost({
body,
options,
}: {
body: API.RoomTypeMapping;
options?: { [key: string]: unknown };
}) {
return request<API.RRoomTypeMapping>('/hotelStaff/typeMapping/getInfo', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
data: body,
...(options || {}),
});
}
/** 查询房型映射列表 查询房型映射列表查询房型映射列表 POST /hotelStaff/typeMapping/pageList */
export function hotelStaffTypeMappingPageListUsingPost({ export function hotelStaffTypeMappingPageListUsingPost({
body, body,
options, options,
@@ -58,24 +40,6 @@ export function hotelStaffTypeMappingPageListUsingPost({
}); });
} }
/** 删除房型映射 删除房型映射删除房型映射 POST /hotelStaff/typeMapping/remove */
export function hotelStaffTypeMappingRemoveUsingPost({
body,
options,
}: {
body: API.RoomTypeMapping;
options?: { [key: string]: unknown };
}) {
return request<API.RBoolean>('/hotelStaff/typeMapping/remove', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
data: body,
...(options || {}),
});
}
/** 修改房型映射 修改房型映射修改房型映射 POST /hotelStaff/typeMapping/update */ /** 修改房型映射 修改房型映射修改房型映射 POST /hotelStaff/typeMapping/update */
export function hotelStaffTypeMappingUpdateUsingPost({ export function hotelStaffTypeMappingUpdateUsingPost({
body, body,

View File

@@ -40,22 +40,14 @@ export type HotelStaffPcUserUpdatePasswordUsingPostResponses = {
200: RBoolean; 200: RBoolean;
}; };
export type HotelStaffTypeMappingAddUsingPostResponses = { export type HotelStaffTypeMappingListUsingPostResponses = {
200: RBoolean; 200: RListRoomTypeMapping;
};
export type HotelStaffTypeMappingGetInfoUsingPostResponses = {
200: RRoomTypeMapping;
}; };
export type HotelStaffTypeMappingPageListUsingPostResponses = { export type HotelStaffTypeMappingPageListUsingPostResponses = {
200: RPageRoomTypeMapping; 200: RPageRoomTypeMapping;
}; };
export type HotelStaffTypeMappingRemoveUsingPostResponses = {
200: RBoolean;
};
export type HotelStaffTypeMappingUpdateUsingPostResponses = { export type HotelStaffTypeMappingUpdateUsingPostResponses = {
200: RBoolean; 200: RBoolean;
}; };
@@ -148,6 +140,12 @@ export type RListPcConfigChannel = {
data?: PcConfigChannel[]; data?: PcConfigChannel[];
}; };
export type RListRoomTypeMapping = {
code?: number;
msg?: string;
data?: RoomTypeMapping[];
};
export type RoomTypeMapping = { export type RoomTypeMapping = {
/** 创建者创建人 */ /** 创建者创建人 */
createBy?: string; createBy?: string;
@@ -187,12 +185,6 @@ export type RPageRoomTypeMapping = {
data?: PageRoomTypeMapping; data?: PageRoomTypeMapping;
}; };
export type RRoomTypeMapping = {
code?: number;
msg?: string;
data?: RoomTypeMapping;
};
export type UpdatePasswordForm = { export type UpdatePasswordForm = {
/** 旧密码 */ /** 旧密码 */
oldPassword?: string; oldPassword?: string;

View File

@@ -3,12 +3,11 @@
<el-form :model="form" :rules="rules" ref="formRef" label-position="top" class="pl-4 pr-4 pt-4"> <el-form :model="form" :rules="rules" ref="formRef" label-position="top" class="pl-4 pr-4 pt-4">
<el-form-item label="选择房型" prop="roomType"> <el-form-item label="选择房型" prop="roomType">
<el-select v-model="form.roomType" placeholder="请选择房型"> <el-select v-model="form.roomType" placeholder="请选择房型">
<el-option label="单人间" value="single"></el-option> <el-option v-for="item in roomList" :label="item.pmsName" :value="item.id" />
<el-option label="双人间" value="double"></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="选择日期" prop="date"> <el-form-item label="选择日期" prop="range">
<el-date-picker v-model="ranger" type="daterange" value-format="YYYY-MM-DD" placeholder="请选择日期" <el-date-picker v-model="form.range" type="daterange" value-format="YYYY-MM-DD" placeholder="请选择日期"
style="width: 100%"> style="width: 100%">
</el-date-picker> </el-date-picker>
</el-form-item> </el-form-item>
@@ -24,30 +23,37 @@
<script setup lang="ts"> <script setup lang="ts">
import { taskCenterItem } from '@constant/taskCenterList' import { taskCenterItem } from '@constant/taskCenterList'
import { hotelStaffTypeMappingListUsingPost } from '@api/index'
const isVisible = ref(false) const isVisible = ref(false)
const title = ref('') const title = ref('')
const formRef = ref()
const form = ref({ const form = ref({
roomType: '', roomType: '',
startTime: '',
endTime: '',
operation: '', operation: '',
range: [],
}) })
const rules = ref({ const rules = ref({
roomType: [ roomType: [
{ required: true, message: '请选择房型', trigger: 'blur' }, { required: true, message: '请选择房型', trigger: 'blur' },
], ],
ranger: [ range: [
{ required: true, message: '请选择日期范围', trigger: 'blur' }, {
required: true,
message: '请选择日期范围',
trigger: 'change',
type: 'array'
},
], ],
}) })
const ranger = ref([])
// 打开弹窗 // 打开弹窗
const open = ({ type }: taskCenterItem) => { const open = ({ type }: taskCenterItem) => {
title.value = type === 'open' ? '开启渠道房型' : '关闭渠道房型' title.value = type === 'open' ? '开启渠道房型' : '关闭渠道房型'
isVisible.value = true isVisible.value = true
form.value.operation = type form.value.operation = type
getRoomTypeList()
} }
// 关闭弹窗 // 关闭弹窗
@@ -58,9 +64,9 @@ const close = () => {
// 重置form // 重置form
const reset = () => { const reset = () => {
form.value.roomType = '' form.value.roomType = ''
form.value.startTime = '' form.value.range = []
form.value.endTime = ''
ranger.value = [] formRef.value.resetFields()
} }
// 取消操作 // 取消操作
@@ -71,14 +77,33 @@ const cancel = () => {
// 确认操作 // 确认操作
const confirm = () => { const confirm = () => {
close() formRef.value.validate((valid: boolean) => {
form.value.startTime = ranger.value[0] if (!valid) {
form.value.endTime = ranger.value[1] return
console.log(form.value) }
/**
* 坑传给进程的参数不能是ref包裹的reactive对象 close()
*/ console.log(form.value)
window.api.executeScript({ ...form.value }) const options = {
roomType: form.value.roomType,
startTime: form.value.range[0],
endTime: form.value.range[1],
operation: form.value.operation,
}
/**
* 坑传给进程的参数不能是ref包裹的reactive对象
*/
window.api.executeScript(options)
reset()
})
}
// 获取房型列表
const roomList: any = ref([])
const getRoomTypeList = async () => {
const res = await hotelStaffTypeMappingListUsingPost({ body: {} })
roomList.value = res.data
} }
defineExpose({ defineExpose({

View File

@@ -46,6 +46,8 @@
"src/renderer/**/*.vue", "src/renderer/**/*.vue",
"**/*.d.ts", "**/*.d.ts",
"**/*.ts", "**/*.ts",
"src/renderer/permission.ts" "**/*.js",
"src/renderer/permission.ts",
"src/main/scripts/*.js"
] ]
} }

View File

@@ -10,8 +10,7 @@
"@assets/*": ["src/assets/*"], "@assets/*": ["src/assets/*"],
"@common/*": ["src/common/*"], "@common/*": ["src/common/*"],
"@service/*": ["src/main/service/*"], "@service/*": ["src/main/service/*"],
"@locales/*": ["locales/*"], "@locales/*": ["locales/*"]
"@utils/*": ["src/main/utils/*"]
} }
} }
} }