Repository: opentiny/tiny-engine
Pull request #1365: fix: copy pop-up modal logic displays abnormally
Author: login: SonyLeo
Labels: name: bug
description: Something isn't working
name: release
description: merge to release/ branch, before release period
Created: 2025-05-07T09:23:39Z | Merged: 2025-05-12T13:26:51Z

English | [简体中文](https://github.com/opentiny/tiny-engine/blob/develop/.github/PULL_REQUEST_TEMPLATE/PULL_REQUEST_TEMPLATE.zh-CN.md)

# PR

## PR Checklist

Please check if your PR fulfills the following requirements:

- [x] The commit message follows our [Commit Message Guidelines](https://github.com/opentiny/tiny-engine/blob/develop/CONTRIBUTING.md)
- [ ] Tests for the changes have been added (for bug fixes / features)
- [ ] Docs have been added / updated (for bug fixes / features)
- [ ] Built its own designer, fully self-validated

## PR Type

What kind of change does this PR introduce?

<!-- Please check the one that applies to this PR using "x". -->

- [x] Bugfix
- [ ] Feature
- [ ] Code style update (formatting, local variables)
- [ ] Refactoring (no functional changes, no api changes)
- [ ] Build related changes
- [ ] CI related changes
- [ ] Documentation content changes
- [ ] Other... Please describe:

## Background and solution
【备注】
https://github.com/opentiny/tiny-engine/pull/1361 为 前置 PR： 解决页面复制路由更新问题

【问题描述】
  点击复制页面，弹出"您即将复制的页面有更改未保存，是否确定跳过更改直接复制？"，但复制页面并没有更改未保存

【问题分析】
 点击 复制页面 后，会执行下述步骤:
 
 1. 调用 `copyPage()`  对当前页面的信息进行保存和更新；
 2. 调用 `isCurrentDataSame()` 对 `data` 和 `dataCopy` 进行对比；
 3. 根据 `isCurrentDataSame()` 的返回值决定是否展示 “更改未保存” 弹窗；
 
 【结论】
 
 第二步对比错误，导致弹窗总是出现，由下述原因导致：
 1. 使用 java 后台时，接口的相应信息中返回的属性值包含空数组；
 2. 点击 复制页面后，页面内容更新时，当前配置页面的数据 `pageSettingState.currentPageData` 未更新，
     `isCurrentDataSame()` 中没有对 `page_content` 中的 `children` 信息进行对比
     
【解决方案】

1. 增加对空数组和空对象的判断
2. 更新 `pageSettingState.currentPageData` ，
   `isCurrentDataSame()` 中增加对  `page_content` 中的 `children` 信息进行对比
3. `syncPageContent` 增加判断：只有当前页面设置的ID与正在编辑的页面ID一致时才同步内容，避免将正在编辑的页面内容错误同步到其他打开的页面设置中

【测试场景】
1. 打开应用 A 页面，打开页面管理插件，
    打开 A 页面的设置页面。点击复制，表现正常✅

2. 打开应用 A 页面，编辑，不保存。
   打开页面管理插件，打开 A 页面的设置页面。
   点击复制，提示未保存。表现正常✅,
   点击工具栏保存按钮，页面提示为保存状态。
   再次点击复制按钮，表现正常✅
 
### What is the current behavior?


<!-- Please describe the current behavior that you are modifying, or link to a relevant issue. -->

Issue Number: N/A

### What is the new behavior?

## Does this PR introduce a breaking change?

- [ ] Yes
- [x] No

<!-- If this PR contains a breaking change, please describe the impact and migration path for existing applications below. -->

## Other information


<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

## Summary by CodeRabbit

- **Bug Fixes**
	- Improved accuracy when detecting changes in page data, reducing false positives when comparing arrays, objects, or primitive values.
	- Enhanced synchronization of page content to ensure consistency before data comparisons.
	- Updated page settings immediately after saving to keep data consistent and up to date.
- **New Features**
	- Added automatic notification to update page settings after a page is saved, improving real-time data consistency across components.
	- Implemented reactive subscription management to handle page save events, ensuring page settings update automatically and subscriptions are cleaned up appropriately.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/packages/plugins/page/src/PageSetting.vue b/packages/plugins/page/src/PageSetting.vue
index 91d6b94c66..a0e859e51d 100644
--- a/packages/plugins/page/src/PageSetting.vue
+++ b/packages/plugins/page/src/PageSetting.vue
@@ -60,7 +60,7 @@
 </template>
 
 <script lang="jsx">
-import { reactive, ref, computed } from 'vue'
+import { reactive, ref, computed, onActivated, onDeactivated } from 'vue'
 import { Button, Collapse, CollapseItem, Input } from '@opentiny/vue'
 import { PluginSetting, ButtonGroup, SvgButton, LifeCycles } from '@opentiny/tiny-engine-common'
 import {
@@ -71,7 +71,8 @@ import {
   useNotify,
   getMergeRegistry,
   getMetaApi,
-  META_SERVICE
+  META_SERVICE,
+  useMessage
 } from '@opentiny/tiny-engine-meta-register'
 import { extend, isEqual } from '@opentiny/vue-renderless/common/object'
 import { constants } from '@opentiny/tiny-engine-utils'
@@ -133,7 +134,8 @@ export default {
       isCurrentDataSame,
       initCurrentPageData,
       isTemporaryPage,
-      STATIC_PAGE_GROUP_ID
+      STATIC_PAGE_GROUP_ID,
+      updatePageSettingAfterSave
     } = usePage()
     const { pageState, initData } = useCanvas()
     const { confirm } = useModal()
@@ -144,6 +146,28 @@ export default {
 
     const { PLUGIN_NAME, getPluginByLayout } = useLayout()
     const align = computed(() => getPluginByLayout(PLUGIN_NAME.AppManage))
+    const { subscribe, unsubscribe } = useMessage()
+
+    // 初始化订阅
+    let subscriber = null
+
+    // 组件激活时订阅
+    onActivated(() => {
+      subscriber = subscribe({
+        topic: 'page-saved',
+        callback: () => {
+          // 当收到页面保存事件时，更新页面设置状态
+          updatePageSettingAfterSave()
+        }
+      })
+    })
+
+    // 组件卸载或失活时取消订阅
+    onDeactivated(() => {
+      if (subscriber) {
+        unsubscribe(subscriber)
+      }
+    })
 
     const state = reactive({
       activeName: Object.values(PAGE_SETTING_SESSION),
diff --git a/packages/plugins/page/src/composable/usePage.ts b/packages/plugins/page/src/composable/usePage.ts
index 851ee4cf97..5e02de67ce 100644
--- a/packages/plugins/page/src/composable/usePage.ts
+++ b/packages/plugins/page/src/composable/usePage.ts
@@ -11,7 +11,7 @@
  */
 
 import { reactive, ref } from 'vue'
-import { extend, isEqual } from '@opentiny/vue-renderless/common/object'
+import { extend, isEqual as isValuesEqual } from '@opentiny/vue-renderless/common/object'
 import { constants } from '@opentiny/tiny-engine-utils'
 import { getCanvasStatus } from '@opentiny/tiny-engine-common/js/canvas'
 import {
@@ -153,7 +153,40 @@ const getDefaultPage = () => {
   }
 }
 
+/**
+ * 更新配置页面数据
+ */
+const syncPageContent = () => {
+  const { getBaseInfo } = getMetaApi(META_SERVICE.GlobalService)
+
+  // 只有当前页面设置的ID与正在编辑的页面ID一致时才同步内容
+  // 避免将正在编辑的页面内容错误同步到其他打开的页面设置中
+  if (pageSettingState.currentPageData.id === Number.parseInt(getBaseInfo().pageId)) {
+    const pageContent = useCanvas().getPageSchema()
+
+    // 此处赋值是确保页面内容与当前画布保持同步。
+    // 当用户在画布中编辑页面内容时，这些变更存储在Canvas内部状态，
+    // 但pageSettingState中的currentPageData.page_content并不会自动更新。
+    // 不同步会导致：
+    // 1. 保存检测失效 - isCurrentDataSame()无法检测到真实变更
+    // 2. 未保存提示缺失 - 用户离开页面时不会收到未保存变更警告
+    pageSettingState.currentPageData.page_content = pageContent
+  }
+}
+
+/**
+ * 在页面保存后更新页面设置状态
+ * 当用户通过工具栏保存按钮保存页面时，需要调用此方法来同步currentPageDataCopy
+ * 以确保isCurrentDataSame()能够正确判断页面是否被修改
+ */
+const updatePageSettingAfterSave = () => {
+  syncPageContent()
+  pageSettingState.currentPageDataCopy = extend(true, {}, pageSettingState.currentPageData)
+}
+
 const isCurrentDataSame = () => {
+  syncPageContent()
+
   const data: Record<string, any> = pageSettingState.currentPageData || {}
   const dataCopy: Record<string, any> = pageSettingState.currentPageDataCopy || {}
   let isEqual = true
@@ -166,19 +199,21 @@ const isCurrentDataSame = () => {
       const obj = {
         inputs: dataCopy[item].inputs,
         outputs: dataCopy[item].outputs,
-        lifeCycles: dataCopy[item].lifeCycles
+        lifeCycles: dataCopy[item].lifeCycles,
+        children: dataCopy[item].children
       }
       const objCopy = {
         inputs: data[item].inputs,
         outputs: data[item].outputs,
-        lifeCycles: data[item].lifeCycles
+        lifeCycles: data[item].lifeCycles,
+        children: data[item].children
       }
 
       if (JSON.stringify(obj) !== JSON.stringify(objCopy)) {
         isEqual = false
       }
     } else {
-      if (dataCopy[item] !== data[item]) {
+      if (!isValuesEqual(dataCopy[item], data[item])) {
         isEqual = false
       }
     }
@@ -235,7 +270,7 @@ const resetPageData = () => {
 }
 
 // 判断当前页面内容是否有修改
-const isChangePageData = () => !isEqual(pageSettingState.currentPageData, pageSettingState.currentPageDataCopy)
+const isChangePageData = () => !isValuesEqual(pageSettingState.currentPageData, pageSettingState.currentPageDataCopy)
 
 const generateTree = (data: PageData[]) => {
   const { ROOT_ID } = pageSettingState
@@ -463,7 +498,10 @@ const switchPageWithConfirm = (pageId: string, clearPreview = false) => {
   })
 }
 
-const updatePageContent = (familyPages: { id: any; page_content: any }[], currentPage: { id: string; page_content?: any }) => {
+const updatePageContent = (
+  familyPages: { id: any; page_content: any }[],
+  currentPage: { id: string; page_content?: any }
+) => {
   const currentPageSchema = familyPages.find((item) => item.id === currentPage.id)
   // 替换为当前页面最新的 schema
   if (currentPageSchema) {
@@ -549,6 +587,7 @@ export default () => {
     switchPageWithConfirm,
     getFamily,
     getPageChildren,
+    updatePageSettingAfterSave,
     STATIC_PAGE_GROUP_ID,
     COMMON_PAGE_GROUP_ID
   }
diff --git a/packages/toolbars/save/src/js/index.ts b/packages/toolbars/save/src/js/index.ts
index 6b87919b69..c8362e2b38 100644
--- a/packages/toolbars/save/src/js/index.ts
+++ b/packages/toolbars/save/src/js/index.ts
@@ -19,12 +19,15 @@ import {
   usePage,
   getOptions,
   getMetaApi,
-  META_APP
+  META_APP,
+  useMessage
 } from '@opentiny/tiny-engine-meta-register'
 import { constants } from '@opentiny/tiny-engine-utils'
 import { handlePageUpdate } from '@opentiny/tiny-engine-common/js/http'
 import meta from '../../meta'
 
+const { publish } = useMessage()
+
 const { PAGE_STATUS, AUTO_SAVED } = constants
 const state = reactive({
   visible: false,
@@ -64,6 +67,9 @@ const savePage = async (pageSchema: any) => {
   }
   await handlePageUpdate(updateParams)
   isLoading.value = false
+
+  // 发布页面保存事件，通知其他组件进行相应处理
+  publish({ topic: 'page-saved' })
 }
 
 export const saveCommon = (value: string) => {


Reviews:
user: login: gene9831
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-08T08:08:02Z
user: login: chilingling
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-08T12:51:02Z
user: login: SonyLeo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-08T13:14:05Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>🧹 Nitpick comments (1)</summary><blockquote>

<details>
<summary>packages/toolbars/save/src/js/index.ts (1)</summary><blockquote>

`67-69`: **Excellent addition to fix the data synchronization issue.**

Adding a call to `updatePageSettingAfterSave()` after the page update completes ensures that the internal data copy (`currentPageDataCopy`) is synchronized with the latest saved state. This directly addresses the issue described in the PR objectives where the copy pop-up modal was incorrectly appearing after saving because `currentPageDataCopy` wasn't updated. This change will prevent the system from incorrectly detecting unsaved changes after a save operation.



Consider wrapping this call in a try-catch block similar to other function calls in this file (like `saved()` on line 175), to maintain consistent error handling throughout the codebase:

```diff
  isLoading.value = false

  // 更新页面设置状态，同步 currentPageDataCopy
+ try {
    usePage().updatePageSettingAfterSave()
+ } catch (error) {
+   useNotify({
+     type: 'error',
+     message: `Error in updating page settings: ${error}`
+   })
+ }
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 4b733ff5390bb5d86008992854dfdf83d48474aa and c4c275b01900daac76bd54f7fcd2fd15036dbc9e.

</details>

<details>
<summary>📒 Files selected for processing (2)</summary>

* `packages/plugins/page/src/composable/usePage.ts` (6 hunks)
* `packages/toolbars/save/src/js/index.ts` (1 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (1)</summary>

* packages/plugins/page/src/composable/usePage.ts

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (1)</summary>

* GitHub Check: push-check

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-12T00:58:24Z
user: login: chilingling
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-12T02:39:21Z
user: login: SonyLeo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-12T07:34:20Z
user: login: chilingling
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-12T08:46:51Z
user: login: chilingling
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-12T12:20:06Z
user: login: SonyLeo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-12T13:00:39Z
user: login: chilingling
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-12T13:25:22Z

Review comments:
diff_hunk: @@ -166,19 +197,21 @@ const isCurrentDataSame = () => {
       const obj = {
         inputs: dataCopy[item].inputs,
         outputs: dataCopy[item].outputs,
-        lifeCycles: dataCopy[item].lifeCycles
+        lifeCycles: dataCopy[item].lifeCycles,
+        children: dataCopy[item].children
       }
       const objCopy = {
         inputs: data[item].inputs,
         outputs: data[item].outputs,
-        lifeCycles: data[item].lifeCycles
+        lifeCycles: data[item].lifeCycles,
+        children: data[item].children
       }
 
       if (JSON.stringify(obj) !== JSON.stringify(objCopy)) {
         isEqual = false
       }
     } else {
-      if (dataCopy[item] !== data[item]) {
+      if (!compareValues(dataCopy[item], data[item])) {
path: packages/plugins/page/src/composable/usePage.ts
user: login: gene9831
body: '@opentiny/vue-renderless/common/object' 提供了 `isEqual` 工具函数，可以改成这个
created_at: 2025-05-08T08:07:25Z
author_association: COLLABORATOR
diff_hunk: @@ -153,7 +153,38 @@ const getDefaultPage = () => {
   }
 }
 
+/**
+ * 比较两个值是否相等: 处理对象、数组和原始类型的情况
+ * @param value1 数值1
+ * @param value2 数值2
+ * @returns 是否相等
+ */
+const compareValues = (value1: any, value2: any): boolean => {
+  // 处理数组比较
+  if (Array.isArray(value1) && Array.isArray(value2)) {
+    return value1.length === value2.length && JSON.stringify(value1) === JSON.stringify(value2)
+  }
+  // 处理对象比较
+  else if (typeof value1 === 'object' && value1 !== null && typeof value2 === 'object' && value2 !== null) {
+    return JSON.stringify(value1) === JSON.stringify(value2)
+  }
+  // 处理原始类型比较
+  else {
+    return value1 === value2
+  }
+}
+
+/**
+ * 更新配置页面数据
+ */
+const syncPageContent = () => {
+  const pageContent = useCanvas().getPageSchema()
+  pageSettingState.currentPageData.page_content = pageContent
+}
+
 const isCurrentDataSame = () => {
+  syncPageContent()
path: packages/plugins/page/src/composable/usePage.ts
user: login: gene9831
body: 这里新加的逻辑建议加上代码注释
created_at: 2025-05-08T08:07:49Z
author_association: COLLABORATOR
diff_hunk: @@ -153,7 +153,23 @@ const getDefaultPage = () => {
   }
 }
 
+/**
+ * 更新配置页面数据
+ */
+const syncPageContent = () => {
+  const pageContent = useCanvas().getPageSchema()
+  // 此处赋值是确保页面内容与当前画布保持同步。
+  // 当用户在画布中编辑页面内容时，这些变更存储在Canvas内部状态，
+  // 但pageSettingState中的currentPageData.page_content并不会自动更新。
+  // 不同步会导致：
+  // 1. 保存检测失效 - isCurrentDataSame()无法检测到真实变更
+  // 2. 未保存提示缺失 - 用户离开页面时不会收到未保存变更警告
+  pageSettingState.currentPageData.page_content = pageContent
+}
+
 const isCurrentDataSame = () => {
+  syncPageContent()
path: packages/plugins/page/src/composable/usePage.ts
user: login: chilingling
body: 不能无脑同步。需要考虑一下场景：
1. 当前在画布编辑页面 A。
2. 我打开页面 B 的设置页面，点击复制等操作，这样会意外导致将编辑页面A的数据同步至当前页面 B 的数据。
created_at: 2025-05-08T12:51:02Z
author_association: MEMBER
diff_hunk: @@ -153,7 +153,23 @@ const getDefaultPage = () => {
   }
 }
 
+/**
+ * 更新配置页面数据
+ */
+const syncPageContent = () => {
+  const pageContent = useCanvas().getPageSchema()
+  // 此处赋值是确保页面内容与当前画布保持同步。
+  // 当用户在画布中编辑页面内容时，这些变更存储在Canvas内部状态，
+  // 但pageSettingState中的currentPageData.page_content并不会自动更新。
+  // 不同步会导致：
+  // 1. 保存检测失效 - isCurrentDataSame()无法检测到真实变更
+  // 2. 未保存提示缺失 - 用户离开页面时不会收到未保存变更警告
+  pageSettingState.currentPageData.page_content = pageContent
+}
+
 const isCurrentDataSame = () => {
+  syncPageContent()
path: packages/plugins/page/src/composable/usePage.ts
user: login: SonyLeo
body: 已在 `syncPageContent()` 中增加 当前页面设置的ID与正在编辑的页面ID一致时才同步内容 的判断逻辑
created_at: 2025-05-08T13:14:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -64,6 +64,9 @@ const savePage = async (pageSchema: any) => {
   }
   await handlePageUpdate(updateParams)
   isLoading.value = false
+
+  // 更新页面设置状态，同步 currentPageDataCopy
+  usePage().updatePageSettingAfterSave()
path: packages/toolbars/save/src/js/index.ts
user: login: chilingling
body: 保存插件和 `pageService` 不要再加强依赖关系。
可以考虑如下思路：
1. 使用 `useCanvas.isSaved()` 监听保存状态的变化，保存之后同步。
2. 保存插件发布 `page-saved` 事件，提供其他插件进行监听和调用。
created_at: 2025-05-12T02:39:21Z
author_association: MEMBER
diff_hunk: @@ -64,6 +64,9 @@ const savePage = async (pageSchema: any) => {
   }
   await handlePageUpdate(updateParams)
   isLoading.value = false
+
+  // 更新页面设置状态，同步 currentPageDataCopy
+  usePage().updatePageSettingAfterSave()
path: packages/toolbars/save/src/js/index.ts
user: login: SonyLeo
body: 已实现
created_at: 2025-05-12T07:34:20Z
author_association: CONTRIBUTOR
diff_hunk: @@ -153,7 +156,49 @@ const getDefaultPage = () => {
   }
 }
 
+/**
+ * 更新配置页面数据
+ */
+const syncPageContent = () => {
+  const { getBaseInfo } = getMetaApi(META_SERVICE.GlobalService)
+
+  // 只有当前页面设置的ID与正在编辑的页面ID一致时才同步内容
+  // 避免将正在编辑的页面内容错误同步到其他打开的页面设置中
+  if (pageSettingState.currentPageData.id === Number.parseInt(getBaseInfo().pageId)) {
+    const pageContent = useCanvas().getPageSchema()
+
+    // 此处赋值是确保页面内容与当前画布保持同步。
+    // 当用户在画布中编辑页面内容时，这些变更存储在Canvas内部状态，
+    // 但pageSettingState中的currentPageData.page_content并不会自动更新。
+    // 不同步会导致：
+    // 1. 保存检测失效 - isCurrentDataSame()无法检测到真实变更
+    // 2. 未保存提示缺失 - 用户离开页面时不会收到未保存变更警告
+    pageSettingState.currentPageData.page_content = pageContent
+  }
+}
+
+/**
+ * 在页面保存后更新页面设置状态
+ * 当用户通过工具栏保存按钮保存页面时，需要调用此方法来同步currentPageDataCopy
+ * 以确保isCurrentDataSame()能够正确判断页面是否被修改
+ */
+const updatePageSettingAfterSave = () => {
+  syncPageContent()
+  pageSettingState.currentPageDataCopy = extend(true, {}, pageSettingState.currentPageData)
+}
+
+// 监听页面保存事件，当收到page-saved事件时，自动更新页面设置状态
+subscribe({
+  topic: 'page-saved',
+  callback: () => {
+    // 当收到页面保存事件时，更新页面设置状态
+    updatePageSettingAfterSave()
+  }
+})
path: packages/plugins/page/src/composable/usePage.ts
user: login: chilingling
body: 1. 建议增加 `subscriber`，方便取消订阅
2. 建议不要放到 `usePage` 顶层函数监听，这个同步函数应该只有打开了页面设置的时候才需要，因为打开设置页面的时候，就默认会同步一次。可以考虑放到 `PageSetting.vue` 来监听？
created_at: 2025-05-12T08:46:51Z
author_association: MEMBER
diff_hunk: @@ -144,6 +146,21 @@ export default {
 
     const { PLUGIN_NAME, getPluginByLayout } = useLayout()
     const align = computed(() => getPluginByLayout(PLUGIN_NAME.AppManage))
+    const { subscribe } = useMessage()
+
+    // 监听页面保存事件
+    const unsubscribePageSaved = subscribe({
path: packages/plugins/page/src/PageSetting.vue
user: login: chilingling
body: 1. `subscribe` 方法返回的好像不是 `unsubscribe` 吧？

https://github.com/opentiny/tiny-engine/blob/f693484d42656ebf994162d1a5df8035d0118b73/packages/register/src/useMessage.ts#L26-L47


2. 当前 `PageSetting.vue` 组件不需要打开就会自动 `Mounted`，用 `unMounnted` 可能无法达到节流监听的效果。可能用 `onActivated` 和 `onDeactivated` 会好一点？

created_at: 2025-05-12T12:20:06Z
author_association: MEMBER
diff_hunk: @@ -144,6 +146,21 @@ export default {
 
     const { PLUGIN_NAME, getPluginByLayout } = useLayout()
     const align = computed(() => getPluginByLayout(PLUGIN_NAME.AppManage))
+    const { subscribe } = useMessage()
+
+    // 监听页面保存事件
+    const unsubscribePageSaved = subscribe({
path: packages/plugins/page/src/PageSetting.vue
user: login: SonyLeo
body: 1. subscribe 方法返回的不是 unsubscribe 函数， 而是一个 subscriber 订阅对象

2. 经验证，用 onActivated 和 onDeactivated 会达到节流监听的效果
created_at: 2025-05-12T13:00:38Z
author_association: CONTRIBUTOR

PR comments:
user: login: coderabbitai[bot]
created_at: 2025-05-07T09:23:46Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough

The import `isEqual` was renamed to `isValuesEqual` and used to replace strict inequality checks with deep equality checks in multiple functions. A new function `syncPageContent` was added to synchronize page content before comparisons. The `isCurrentDataSame` function now compares additional properties including `children`. The `updatePageSettingAfterSave` function was added to synchronize page data after saving and is triggered by a `page-saved` event. The `updatePageContent` parameters were reformatted to multi-line style. The `savePage` function was updated to publish a `page-saved` event after completing the page save. The `PageSetting.vue` component subscribes to the `page-saved` event to call `updatePageSettingAfterSave` and cleans up the subscription on component unmount.

## Changes

| File                                            | Change Summary                                                                                                                             |
|-------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------|
| packages/plugins/page/src/composable/usePage.ts | Renamed import to `isValuesEqual`; replaced strict inequality checks with deep equality checks; added `syncPageContent` to sync page content; enhanced `isCurrentDataSame` to compare `children` property; added `updatePageSettingAfterSave` to sync page data after save; reformatted `updatePageContent` parameters to multi-line style; exported `updatePageSettingAfterSave`. |
| packages/toolbars/save/src/js/index.ts           | Imported `useMessage` and published a `page-saved` event after the asynchronous page save completes to notify other components.             |
| packages/plugins/page/src/PageSetting.vue         | Subscribed to `page-saved` event using `useMessage`; triggered `updatePageSettingAfterSave` on event; cleaned up subscription on component unmount. |

## Poem

> In the warren of code, a new check hops in,  
> Comparing deep, where bugs might begin.  
> With `isValuesEqual`, we peer through the hay,  
> Ensuring our data won’t lead us astray.  
> Multiline whispers in function’s embrace—  
> A tidy-up touch for a hoppier place! 🐇✨  
> After saving, sync calls dance in line,  
> Keeping page data fresh and fine! 🎉🐰

<!-- walkthrough_end -->

<!-- announcements_start -->

> [!TIP]
> <details>
> <summary>⚡️ Faster reviews with caching</summary>
> 
> - CodeRabbit now supports caching for code and dependencies, helping speed up reviews. This means quicker feedback, reduced wait times, and a smoother review experience overall. Cached data is encrypted and stored securely. This feature will be automatically enabled for all accounts on May 16th. To opt out, configure `Review - Disable Cache` at either the organization or repository level. If you prefer to disable all data retention across your organization, simply turn off the `Data Retention` setting under your Organization Settings.
> 
> Enjoy the performance boost—your workflow just got faster.
> 
> </details>

<!-- announcements_end -->

---

<details>
<summary>📜 Recent review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between d4347269f57fb9542e9beeca3c44736070c8eab5 and 786cc4eca737ee0e807395a1f9acbcc236f13310.

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `packages/plugins/page/src/PageSetting.vue` (4 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (1)</summary>

* packages/plugins/page/src/PageSetting.vue

</details>

</details>
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- tips_start -->

---

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=opentiny/tiny-engine&utm_content=1365):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

### Support

Need help? Create a ticket on our [support page](https://www.coderabbit.ai/contact-us/support) for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai generate sequence diagram` to generate a sequence diagram of the changes in this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: CONTRIBUTOR
user: login: chilingling
created_at: 2025-05-09T10:44:40Z
body: 场景：
1. 打开应用 A 页面，编辑，不保存。
2. 打开页面管理插件，打开 A 页面的设置页面。
3. 点击复制，提示未保存。表现正常✅
4. 点击工具栏保存按钮，页面提示为保存状态。
5. 再次点击复制按钮，仍然提示未保存，表现不正确 ❌
author_association: MEMBER
user: login: Issues-translate-bot
created_at: 2025-05-09T10:44:49Z
body:  
> Bot detected the issue body's language is not English, translate it automatically.         
----    
Scene:
1. Open the App A page, edit, and do not save.
2. Open the page management plug-in and open the settings page of page A.
3. Click Copy, and the prompt is not saved. Normal performance✅
4. Click the toolbar save button, and the page prompts it to be saved.
5. Click the copy button again, and the prompt is still not saved, and the performance is incorrect ❌  
      
author_association: NONE
user: login: SonyLeo
created_at: 2025-05-12T01:42:15Z
body: > 场景：

> 1. 打开应用 A 页面，编辑，不保存。
> 2. 打开页面管理插件，打开 A 页面的设置页面。
> 3. 点击复制，提示未保存。表现正常✅
> 4. 点击工具栏保存按钮，页面提示为保存状态。
> 5. 再次点击复制按钮，仍然提示未保存，表现不正确 ❌

#### 一、问题分析
保存页面时，仅更新了 Canvas 的状态为“已保存”，
但 pageSettingState.currentPageDataCopy 并未同步更新为 pageSettingState.currentPageData 的最新数据。
在之后调用 isCurrentDataSame() 进行比较时，两个对象存在差异，因此错误地判断为存在未保存的更改。

#### 二、结论

 缺乏对 currentPageDataCopy 的更新，导致页面设置状态与 Canvas 状态不一致，引发用户混淆。

#### 三、解决方案

在 usePage.ts 中新增 updatePageSettingAfterSave() 方法，用于同步 currentPageData 和 currentPageDataCopy

修改工具栏中的 savePage() 函数，在保存后调用上述同步方法


author_association: CONTRIBUTOR
user: login: Issues-translate-bot
created_at: 2025-05-12T01:42:24Z
body:  
> Bot detected the issue body's language is not English, translate it automatically.         
----    
> Scenario:

> 1. Open the App A page, edit, not save.
> 2. Open the page management plug-in and open the settings page of page A.
> 3. Click Copy, the prompt is not saved. Normal performance✅
> 4. Click the toolbar save button, and the page prompts it to be saved.
> 5. Click the copy button again, and the prompt is still not saved, and the performance is incorrect ❌

#### 1. Problem analysis
When saving the page, only the Canvas status is updated to "Save".
However, pageSettingState.currentPageDataCopy does not synchronize the latest data updated to pageSettingState.currentPageData.
When the isCurrentDataSame() is called afterward for comparison, there is a difference between the two objects, so it is wrongly judged that there is an unsaved change.

#### 2. Conclusion

 The lack of updates to currentPageDataCopy causes the page setting status to be inconsistent with the Canvas status, causing user confusion.

#### 3. Solution

Added updatePageSettingAfterSave() method in usePage.ts to synchronize currentPageData and currentPageDataCopy

Modify the savePage() function in the toolbar and call the above synchronization method after saving  
      
author_association: NONE