|
@@ -13,6 +13,50 @@ const MINI_LOGIN_URL = `${MINI_BASE_URL}/talent-mini`;
|
|
|
const TOKEN_KEY = 'talent_token';
|
|
const TOKEN_KEY = 'talent_token';
|
|
|
const USER_KEY = 'talent_user';
|
|
const USER_KEY = 'talent_user';
|
|
|
|
|
|
|
|
|
|
+/**
|
|
|
|
|
+ * 人才小程序订单数据类型定义 (Story 13.3)
|
|
|
|
|
+ */
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 订单数据接口
|
|
|
|
|
+ */
|
|
|
|
|
+export interface TalentOrderData {
|
|
|
|
|
+ /** 订单 ID */
|
|
|
|
|
+ id: number;
|
|
|
|
|
+ /** 订单名称 */
|
|
|
|
|
+ name: string;
|
|
|
|
|
+ /** 公司名称 */
|
|
|
|
|
+ companyName?: string;
|
|
|
|
|
+ /** 订单状态 */
|
|
|
|
|
+ status?: string;
|
|
|
|
|
+ /** 创建时间 */
|
|
|
|
|
+ createdAt?: string;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 订单详情数据接口
|
|
|
|
|
+ */
|
|
|
|
|
+export interface TalentOrderDetailData {
|
|
|
|
|
+ /** 订单 ID */
|
|
|
|
|
+ id: number;
|
|
|
|
|
+ /** 订单名称 */
|
|
|
|
|
+ name: string;
|
|
|
|
|
+ /** 公司名称 */
|
|
|
|
|
+ companyName: string;
|
|
|
|
|
+ /** 平台名称 */
|
|
|
|
|
+ platformName?: string;
|
|
|
|
|
+ /** 订单状态 */
|
|
|
|
|
+ status: string;
|
|
|
|
|
+ /** 预计人数 */
|
|
|
|
|
+ expectedCount?: number;
|
|
|
|
|
+ /** 实际人数 */
|
|
|
|
|
+ actualCount?: number;
|
|
|
|
|
+ /** 预计开始日期 */
|
|
|
|
|
+ expectedStartDate?: string;
|
|
|
|
|
+ /** 薪资 */
|
|
|
|
|
+ salary?: number;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
* 人才小程序 Page Object
|
|
* 人才小程序 Page Object
|
|
|
*
|
|
*
|
|
@@ -492,4 +536,264 @@ export class TalentMiniPage {
|
|
|
await expect(this.passwordInputPlaceholder).toBeVisible({ timeout: TIMEOUTS.ELEMENT_VISIBLE_SHORT });
|
|
await expect(this.passwordInputPlaceholder).toBeVisible({ timeout: TIMEOUTS.ELEMENT_VISIBLE_SHORT });
|
|
|
await expect(this.loginButtonText).toBeVisible({ timeout: TIMEOUTS.ELEMENT_VISIBLE_SHORT });
|
|
await expect(this.loginButtonText).toBeVisible({ timeout: TIMEOUTS.ELEMENT_VISIBLE_SHORT });
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ // ===== 我的订单方法 (Story 13.3) =====
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 导航到"我的订单"页面 (Story 13.3)
|
|
|
|
|
+ *
|
|
|
|
|
+ * 人才小程序的"我的订单"页面显示该用户(残疾人)关联的所有订单
|
|
|
|
|
+ *
|
|
|
|
|
+ * @example
|
|
|
|
|
+ * await talentMiniPage.navigateToMyOrders();
|
|
|
|
|
+ */
|
|
|
|
|
+ async navigateToMyOrders(): Promise<void> {
|
|
|
|
|
+ // 点击底部导航的"我的"按钮
|
|
|
|
|
+ const myButton = this.page.getByText('我的', { exact: true }).first();
|
|
|
|
|
+ await myButton.click();
|
|
|
|
|
+
|
|
|
|
|
+ // 等待导航完成
|
|
|
|
|
+ await this.page.waitForTimeout(TIMEOUTS.SHORT);
|
|
|
|
|
+
|
|
|
|
|
+ // 点击"我的订单"菜单项
|
|
|
|
|
+ const myOrdersText = this.page.getByText('我的订单').first();
|
|
|
|
|
+ await myOrdersText.click();
|
|
|
|
|
+
|
|
|
|
|
+ // 等待订单列表页面加载
|
|
|
|
|
+ await this.page.waitForLoadState('domcontentloaded', { timeout: TIMEOUTS.PAGE_LOAD });
|
|
|
|
|
+ await this.page.waitForTimeout(TIMEOUTS.MEDIUM);
|
|
|
|
|
+
|
|
|
|
|
+ console.debug('[人才小程序] 已导航到我的订单页面');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 获取"我的订单"列表 (Story 13.3)
|
|
|
|
|
+ *
|
|
|
|
|
+ * @returns 订单数据数组
|
|
|
|
|
+ * @example
|
|
|
|
|
+ * const orders = await talentMiniPage.getMyOrders();
|
|
|
|
|
+ * console.debug(`找到 ${orders.length} 个订单`);
|
|
|
|
|
+ */
|
|
|
|
|
+ async getMyOrders(): Promise<TalentOrderData[]> {
|
|
|
|
|
+ const orders: TalentOrderData[] = [];
|
|
|
|
|
+
|
|
|
|
|
+ // 查找所有订单卡片
|
|
|
|
|
+ const orderCards = this.page.locator('.bg-white.p-4, .card, [class*="order-card"]');
|
|
|
|
|
+
|
|
|
|
|
+ const count = await orderCards.count();
|
|
|
|
|
+ console.debug(`[人才小程序] 找到 ${count} 个订单卡片`);
|
|
|
|
|
+
|
|
|
|
|
+ for (let i = 0; i < count; i++) {
|
|
|
|
|
+ const card = orderCards.nth(i);
|
|
|
|
|
+ const cardText = await card.textContent();
|
|
|
|
|
+
|
|
|
|
|
+ if (!cardText) continue;
|
|
|
|
|
+
|
|
|
|
|
+ const order: TalentOrderData = {
|
|
|
|
|
+ id: 0,
|
|
|
|
|
+ name: '',
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 提取订单名称(通常是加粗的文本)
|
|
|
|
|
+ const nameElement = card.locator('.font-semibold, .font-bold, .text-lg').first();
|
|
|
|
|
+ const nameCount = await nameElement.count();
|
|
|
|
|
+ if (nameCount > 0) {
|
|
|
|
|
+ order.name = (await nameElement.textContent())?.trim() || '';
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 如果没有找到名称元素,尝试从文本中提取
|
|
|
|
|
+ const lines = cardText.split('\n').map(l => l.trim()).filter(l => l);
|
|
|
|
|
+ if (lines.length > 0) {
|
|
|
|
|
+ order.name = lines[0];
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 提取公司名称
|
|
|
|
|
+ const companyMatch = cardText.match(/公司[::]?\s*([^\n]+)/);
|
|
|
|
|
+ if (companyMatch) {
|
|
|
|
|
+ order.companyName = companyMatch[1].trim();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 提取订单状态
|
|
|
|
|
+ const statusKeywords = ['进行中', '已完成', '草稿', '已确认', '未入职', '已入职', '工作中', '已离职'];
|
|
|
|
|
+ for (const keyword of statusKeywords) {
|
|
|
|
|
+ if (cardText.includes(keyword)) {
|
|
|
|
|
+ order.status = keyword;
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 提取订单 ID(从 URL 或数据属性中)
|
|
|
|
|
+ const cardLink = card.locator('a').or(card);
|
|
|
|
|
+ const href = await cardLink.getAttribute('href');
|
|
|
|
|
+ if (href) {
|
|
|
|
|
+ const idMatch = href.match(/id[=]?(\d+)/);
|
|
|
|
|
+ if (idMatch) {
|
|
|
|
|
+ order.id = parseInt(idMatch[1], 10);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (order.name) {
|
|
|
|
|
+ orders.push(order);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return orders;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 等待订单出现在"我的订单"列表中 (Story 13.3)
|
|
|
|
|
+ *
|
|
|
|
|
+ * 使用轮询机制等待订单出现,用于验证数据同步
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param orderName 订单名称
|
|
|
|
|
+ * @param timeout 超时时间(ms),默认 10000ms
|
|
|
|
|
+ * @returns 是否在超时时间内检测到订单
|
|
|
|
|
+ * @example
|
|
|
|
|
+ * const found = await talentMiniPage.waitForOrderToAppear('测试订单', 10000);
|
|
|
|
|
+ * if (found) {
|
|
|
|
|
+ * console.debug('订单已同步到小程序');
|
|
|
|
|
+ * }
|
|
|
|
|
+ */
|
|
|
|
|
+ async waitForOrderToAppear(orderName: string, timeout: number = 10000): Promise<boolean> {
|
|
|
|
|
+ const startTime = Date.now();
|
|
|
|
|
+ const pollInterval = 500;
|
|
|
|
|
+
|
|
|
|
|
+ while (Date.now() - startTime < timeout) {
|
|
|
|
|
+ // 刷新订单列表
|
|
|
|
|
+ await this.page.evaluate(() => {
|
|
|
|
|
+ window.location.reload();
|
|
|
|
|
+ });
|
|
|
|
|
+ await this.page.waitForLoadState('domcontentloaded', { timeout: TIMEOUTS.PAGE_LOAD });
|
|
|
|
|
+ await this.page.waitForTimeout(TIMEOUTS.SHORT);
|
|
|
|
|
+
|
|
|
|
|
+ // 检查订单是否出现
|
|
|
|
|
+ const orders = await this.getMyOrders();
|
|
|
|
|
+ const found = orders.some(order => order.name === orderName);
|
|
|
|
|
+
|
|
|
|
|
+ if (found) {
|
|
|
|
|
+ const syncTime = Date.now() - startTime;
|
|
|
|
|
+ console.debug(`[人才小程序] 订单 "${orderName}" 已出现,耗时: ${syncTime}ms`);
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ await this.page.waitForTimeout(pollInterval);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ console.debug(`[人才小程序] 订单 "${orderName}" 未在 ${timeout}ms 内出现`);
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 打开订单详情 (Story 13.3)
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param orderName 订单名称
|
|
|
|
|
+ * @returns 订单详情页 URL 中的 ID 参数
|
|
|
|
|
+ * @example
|
|
|
|
|
+ * const orderId = await talentMiniPage.openOrderDetail('测试订单');
|
|
|
|
|
+ * console.debug(`打开了订单详情: ${orderId}`);
|
|
|
|
|
+ */
|
|
|
|
|
+ async openOrderDetail(orderName: string): Promise<string> {
|
|
|
|
|
+ // 查找包含订单名称的卡片并点击
|
|
|
|
|
+ const orderCard = this.page.locator('.bg-white.p-4, .card, [class*="order-card"]').filter({ hasText: orderName }).first();
|
|
|
|
|
+ await orderCard.click();
|
|
|
|
|
+
|
|
|
|
|
+ // 等待导航到详情页
|
|
|
|
|
+ await this.page.waitForURL(
|
|
|
|
|
+ url => url.hash.includes('/pages/talent/order/detail/index') || url.hash.includes('/order/detail'),
|
|
|
|
|
+ { timeout: TIMEOUTS.PAGE_LOAD }
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ // 提取详情页 URL 中的 ID 参数
|
|
|
|
|
+ const afterUrl = this.page.url();
|
|
|
|
|
+ const urlMatch = afterUrl.match(/id[=]?(\d+)/);
|
|
|
|
|
+ const orderId = urlMatch ? urlMatch[1] : '';
|
|
|
|
|
+
|
|
|
|
|
+ console.debug(`[人才小程序] 已打开订单详情: ${orderId}`);
|
|
|
|
|
+ return orderId;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 获取订单详情信息 (Story 13.3)
|
|
|
|
|
+ *
|
|
|
|
|
+ * @returns 订单详情数据
|
|
|
|
|
+ * @example
|
|
|
|
|
+ * const detail = await talentMiniPage.getOrderDetail();
|
|
|
|
|
+ * console.debug(`订单详情: ${detail.name}, 状态: ${detail.status}`);
|
|
|
|
|
+ */
|
|
|
|
|
+ async getOrderDetail(): Promise<TalentOrderDetailData> {
|
|
|
|
|
+ const pageContent = await this.page.textContent('body') || '';
|
|
|
|
|
+
|
|
|
|
|
+ const detail: TalentOrderDetailData = {
|
|
|
|
|
+ id: 0,
|
|
|
|
|
+ name: '',
|
|
|
|
|
+ companyName: '',
|
|
|
|
|
+ status: '',
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 从 URL 中提取订单 ID
|
|
|
|
|
+ const urlMatch = this.page.url().match(/id[=]?(\d+)/);
|
|
|
|
|
+ if (urlMatch) {
|
|
|
|
|
+ detail.id = parseInt(urlMatch[1], 10);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 提取订单名称
|
|
|
|
|
+ const nameMatch = pageContent.match(/订单名称[::]?\s*([^\n]+)/);
|
|
|
|
|
+ if (nameMatch) {
|
|
|
|
|
+ detail.name = nameMatch[1].trim();
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 尝试查找大号标题文本
|
|
|
|
|
+ const titleElement = this.page.locator('.text-xl, .text-lg, .font-bold').first();
|
|
|
|
|
+ const titleText = await titleElement.textContent();
|
|
|
|
|
+ if (titleText) {
|
|
|
|
|
+ detail.name = titleText.trim();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 提取公司名称
|
|
|
|
|
+ const companyMatch = pageContent.match(/公司[::]?\s*([^\n]+)/);
|
|
|
|
|
+ if (companyMatch) {
|
|
|
|
|
+ detail.companyName = companyMatch[1].trim();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 提取平台名称
|
|
|
|
|
+ const platformMatch = pageContent.match(/平台[::]?\s*([^\n]+)/);
|
|
|
|
|
+ if (platformMatch) {
|
|
|
|
|
+ detail.platformName = platformMatch[1].trim();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 提取订单状态
|
|
|
|
|
+ const statusKeywords = ['进行中', '已完成', '草稿', '已确认', '未入职', '已入职', '工作中', '已离职'];
|
|
|
|
|
+ for (const keyword of statusKeywords) {
|
|
|
|
|
+ if (pageContent.includes(keyword)) {
|
|
|
|
|
+ detail.status = keyword;
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 提取预计人数
|
|
|
|
|
+ const expectedCountMatch = pageContent.match(/预计人数[::]?\s*(\d+)/);
|
|
|
|
|
+ if (expectedCountMatch) {
|
|
|
|
|
+ detail.expectedCount = parseInt(expectedCountMatch[1], 10);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 提取实际人数
|
|
|
|
|
+ const actualCountMatch = pageContent.match(/实际人数[::]?\s*(\d+)/);
|
|
|
|
|
+ if (actualCountMatch) {
|
|
|
|
|
+ detail.actualCount = parseInt(actualCountMatch[1], 10);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 提取预计开始日期
|
|
|
|
|
+ const startDateMatch = pageContent.match(/开始日期[::]?\s*(\d{4}-\d{2}-\d{2})/);
|
|
|
|
|
+ if (startDateMatch) {
|
|
|
|
|
+ detail.expectedStartDate = startDateMatch[1];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 提取薪资
|
|
|
|
|
+ const salaryMatch = pageContent.match(/薪资[::]?\s*[¥¥]?(\d+)/);
|
|
|
|
|
+ if (salaryMatch) {
|
|
|
|
|
+ detail.salary = parseInt(salaryMatch[1], 10);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return detail;
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|