2 Commits 42adc20480 ... 06beb2c5d2

Auteur SHA1 Message Date
  YuJing 06beb2c5d2 feat: 课程表的UI和相应的交互 il y a 1 mois
  YuJing a3cbe20c37 feat: 座位表的渲染和相关交互 il y a 1 mois

+ 7 - 0
features/feature/src/main/ets/model/Index.ets

@@ -0,0 +1,7 @@
+// 撤销数据
+@ObservedV2
+export class undoData {
+  arr?: string[]
+  arr2?: Array<string[]>
+  columnMode?: number
+}

+ 123 - 0
features/feature/src/main/ets/pages/ClassSchedulePage.ets

@@ -0,0 +1,123 @@
+import { BasicType } from 'basic';
+import { ClassSchedulePageViewModel } from '../viewModel/ClassSchedulePageViewModel';
+
+@ComponentV2
+struct ClassSchedulePage {
+  @Local vm: ClassSchedulePageViewModel = new ClassSchedulePageViewModel(this.getUIContext());
+
+  build() {
+    NavDestination() {
+      Column({space: 10}){
+        // 功能控制按钮
+        Row(){
+          ForEach(this.vm.controlBtn, (item: BasicType, index) => {
+            this.controlButton(item.text!, item.click!)
+          })
+        }
+        .height(30)
+        .width("100%")
+        .padding({left:128})
+        .justifyContent(FlexAlign.SpaceBetween)
+
+        Scroll(){
+          Row(){
+            // 周
+            Column({space: 30}){
+              ForEach(this.vm.weekList, (item: string, index) => {
+                Row(){
+                  Text(item)
+                    .fontSize(16)
+                    .fontWeight(500)
+                    .fontColor('#333333')
+                }
+                .width(80)
+                .height(30)
+                .borderRadius(10)
+                .backgroundColor(Color.White)
+                .alignItems(VerticalAlign.Center)
+                .justifyContent(FlexAlign.Center)
+                .border({ width: 1, color: '#7186F9'})
+                .padding({top: 7, bottom: 7})
+                .draggable(true)
+              })
+            }
+            .borderRadius(15)
+            .justifyContent(FlexAlign.Start)
+            .alignItems(HorizontalAlign.Center)
+            .padding({ top: 17, left:25, right: 18, bottom: 6 })
+
+            // 课程表
+            Column(){
+              Scroll(){
+                Column({space: 18.4}){
+                  ForEach(this.vm.table, (seating: string[], x: number) => {
+                    Row({space: 18}){
+                      ForEach(seating, (item: string, y: number) => {
+                        Row(){
+                          TextInput({text: item})
+                            .border(null)
+                            .width("100%")
+                            .textAlign(TextAlign.Center)
+                            .backgroundColor(Color.Transparent)
+                            .onFocus(()=> {
+                              this.vm.onFocus(x, y)
+                            })
+                            .onBlur(() => {
+                              this.vm.onFocus(-1, -1)
+                            })
+                        }
+                        .width(66)
+                        .height(40)
+                        .borderRadius(10)
+                        .backgroundColor('#E7EAFF')
+                        .alignItems(VerticalAlign.Center)
+                        .justifyContent(FlexAlign.Center)
+                        .border({
+                          width: this.vm.selectX == x && this.vm.selectY == y ? 2 : 0,   // 边框宽度
+                          color: '#5668FC',        // 边框颜色
+                          style: BorderStyle.Dashed, // 虚线样式
+                        })
+                      })
+                    }
+                  })
+                }
+              }
+              .width("100%")
+              .align(Alignment.TopStart)
+              .scrollable(ScrollDirection.Horizontal)
+            }
+            .layoutWeight(1)
+            .borderRadius(15)
+            .backgroundColor(Color.White)
+            .padding({left: 15, top: 12, right: 15, bottom: 12})
+          }
+          .alignItems(VerticalAlign.Top)
+          .justifyContent(FlexAlign.Start)
+        }
+        .layoutWeight(1)
+      }
+      .width("100%")
+      .height("100%")
+      .backgroundColor('#E7EAFF')
+      .padding({ top: this.vm.safeTop - 10, right: 15, bottom: 17 })
+    }
+    .hideTitleBar(true)
+    .onBackPressed(() => { return this.vm._onBackPressed() })
+  }
+
+  // 控制按钮
+  @Builder controlButton(text: string, onClick: () => void){
+    Text(text)
+      .fontSize(16)
+      .borderRadius(10)
+      .fontColor(Color.White)
+      .backgroundColor('#7186F9')
+      .padding({left: 8, top: 6, right: 8, bottom: 6})
+      .onClick(onClick)
+  }
+}
+
+@Builder
+function ClassSchedulePageBuilder() {
+  ClassSchedulePage()
+}

+ 147 - 24
features/feature/src/main/ets/pages/SeatingPlanPage.ets

@@ -1,51 +1,174 @@
 import { SeatingPlanPageViewModel } from '../viewModel/SeatingPlanPageViewModel';
 import { window } from '@kit.ArkUI';
+import { unifiedDataChannel, uniformTypeDescriptor } from '@kit.ArkData';
+import { BasicType } from 'basic';
 
 // 座位表页面
 @ComponentV2
 struct SeatingPlanPage {
   @Local vm: SeatingPlanPageViewModel = new SeatingPlanPageViewModel(this.getUIContext());
 
-  aboutToAppear() {
-
-  }
-
-  aboutToDisappear(): void {
-    this.vm._onBackPressed()
-  }
-
   build() {
     NavDestination() {
-      Column() {
+      Row({space: 13}){
+        // 学生列表
         Column(){
           List({space: 4}){
-            ForEach(this.vm.dataSource, (item: number, index) => {
+            ForEach(this.vm.dataSource, (item: string, index) => {
               ListItem(){
-                Text('四个字的人')
-                  .width('100%')
-                  .fontSize(14)
-                  .borderRadius(15)
-                  .border({width: 1, color: item == 1 ? '#7186F9' : '#FB9BC1'})
-                  .padding({top: 7, bottom: 7})
+                Row(){
+                  Text(item)
+                    .fontSize(16)
+                    .fontWeight(500)
+                    .fontColor('#333333')
+                }
+                .width(77)
+                .height(30)
+                .borderRadius(10)
+                .backgroundColor(Color.White)
+                .alignItems(VerticalAlign.Center)
+                .justifyContent(FlexAlign.Center)
+                .border({ width: 1, color: index%2 == 0 ? '#7186F9' : '#FB9BC1'})
+                .padding({top: 7, bottom: 7})
+                .draggable(true)
+                .onDragStart((event) => {
+                  let data: unifiedDataChannel.PlainText = new unifiedDataChannel.PlainText();
+                  data.abstract = item;
+                  this.vm._addUndoData()
+                  this.vm.dataSource.splice(index, 1);
+                  (event as DragEvent).setData(new unifiedDataChannel.UnifiedData(data));
+                })
+                .onDragEnd((event) => {
+                  this.vm._onDragEnd(event)
+                })
               }
             })
           }
-          .width('100%')
-          .height('100%')
+          .width("100%")
+          .height("100%")
           .scrollBar(BarState.Off)
         }
         .width(95)
+        .height("100%")
+        .borderRadius(15)
+        .backgroundColor('#E1E5FF')
+        .justifyContent(FlexAlign.Start)
+        .alignItems(HorizontalAlign.Center)
+        .border({width:1, color: '#5668FC'})
+        .padding({ top: 6, left:8, right: 8, bottom: 6 })
+
+        // 操作按钮 和 座位表
+        Column({space: 11}){
+          // 功能控制按钮
+          Row(){
+            ForEach(this.vm.controlBtn, (item: BasicType, index) => {
+              this.controlButton(item.text!, item.click!, item.message ? true : false)
+            })
+          }
+          .height(30)
+          .width("100%")
+          .justifyContent(FlexAlign.SpaceBetween)
+
+          // 座位表
+          Column(){
+            Scroll(){
+              List({space: 24}){
+                ListItem(){
+                  Row(){
+                    Text('讲台')
+                      .fontSize(16)
+                      .fontWeight(500)
+                      .borderRadius(10)
+                      .backgroundColor(Color.White)
+                      .padding({left: 120, top: 8, right: 120, bottom: 8})
+                  }
+                  .padding({left: 200, right: 200})
+                  .justifyContent(FlexAlign.Center)
+                  .alignItems(VerticalAlign.Center)
+                }
+
+                ForEach(this.vm.seatingPlan, (seating: string[], x: number) => {
+                  ListItem(){
+                    Row(){
+                      ForEach(seating, (item: string, y: number) => {
+                        Row(){
+                          Text(item)
+                        }
+                        .width(70)
+                        .height(40)
+                        .borderRadius(10)
+                        .backgroundColor('#E7EAFF')
+                        .alignItems(VerticalAlign.Center)
+                        .justifyContent(FlexAlign.Center)
+                        .margin({right: this.vm.columnMode == 1 ? 14 : ((y+1)%2 == 1 ? 7 : 30)})
+                        .allowDrop([uniformTypeDescriptor.UniformDataType.PLAIN_TEXT])
+                        .onClick(() => { this.vm._onStudentClick(seating, x, y) })
+                        .onDrop((dragEvent?: DragEvent) => {
+                          this.vm.getDataFromUdmf((dragEvent as DragEvent), (event: DragEvent) => {
+                            let records: Array<unifiedDataChannel.UnifiedRecord> = event.getData().getRecords();
+                            let plainText: unifiedDataChannel.PlainText = records[0] as unifiedDataChannel.PlainText;
+
+                            this.vm._onDrag(seating, x, y, plainText.abstract ?? '')
+
+                            event.setResult(DragResult.DRAG_SUCCESSFUL);
+                          })
+                        })
+                      })
+                    }
+                  }
+                })
+              }
+            }
+            .height("100%")
+            .align(Alignment.TopStart)
+            .scrollable(ScrollDirection.Horizontal)
+          }
+          .width('100%')
+          .layoutWeight(1)
+          .alignItems(HorizontalAlign.Center)
+          .justifyContent(FlexAlign.Center)
+        }
+        .layoutWeight(1)
         .height('100%')
-        .padding({top: 6, bottom: 6, left: 8, right: 8})
+        .alignItems(HorizontalAlign.Start)
+        .justifyContent(FlexAlign.Start)
       }
-      .width('100%')
-      .height('100%')
-      .justifyContent(FlexAlign.Center)
-      .alignItems(HorizontalAlign.Start)
+      .width("100%")
+      .height("100%")
+      .backgroundColor('#F5F6FF')
+      .padding({ left: 15, top: this.vm.safeTop, right: 15, bottom: 17 })
     }
     .hideTitleBar(true)
     .onBackPressed(() => { return this.vm._onBackPressed() })
-    .padding({ left: this.vm.safeTop })
+  }
+
+  // 控制按钮
+  @Builder controlButton(text: string, onClick: () => void, isDouble: boolean = false){
+    if(!isDouble) {
+      Text(text)
+        .fontSize(16)
+        .borderRadius(10)
+        .fontColor(Color.White)
+        .backgroundColor('#7186F9')
+        .padding({left: 10, top: 6, right: 10, bottom: 6})
+        .onClick(onClick)
+    } else {
+      Row(){
+        Text('单人')
+          .fontSize(16)
+          .borderRadius({bottomLeft: 10, topLeft: 10})
+          .fontColor(Color.White)
+          .backgroundColor(this.vm.columnMode == 1 ? '#E7EAFF' : '#7186F9')
+          .padding({left: 10, top: 6, right: 10, bottom: 6})
+
+        Text('双人')
+          .fontSize(16)
+          .borderRadius({topRight: 10, bottomRight: 10})
+          .fontColor(Color.White)
+          .backgroundColor(this.vm.columnMode == 2 ? '#E7EAFF' : '#7186F9')
+          .padding({left: 10, top: 6, right: 10, bottom: 6})
+      }.onClick(onClick)
+    }
   }
 }
 

+ 10 - 0
features/feature/src/main/ets/utils/Index.ets

@@ -0,0 +1,10 @@
+export function throttle(_f: () => void, _delay: number = 200){
+  let last: number = 0;
+  return () => {
+    const now: number = Date.now();
+    if(now - last > _delay){
+      _f();
+      last = now;
+    }
+  }
+}

+ 5 - 0
features/feature/src/main/ets/utils/RouterUtils.ets

@@ -36,6 +36,11 @@ class RouterUtils{
     return yTRouter.getParamByName('IncreaseScorePage').pop() as IncreaseScorePageModel
   }
 
+  // 课程表页面
+  router2CourseTablePage(param: ESObject, back: Callback<PopInfo>){
+    yTRouter.pushPathByName('ClassSchedulePage', param, back)
+  }
+
 
   // DiaLog
   // 录入信息弹窗

+ 212 - 0
features/feature/src/main/ets/viewModel/ClassSchedulePageViewModel.ets

@@ -0,0 +1,212 @@
+import { BasicType, IBestToast, YTAvoid, yTRouter } from "basic"
+import { window } from "@kit.ArkUI"
+import { undoData } from "../model/Index"
+
+@ObservedV2
+export class ClassSchedulePageViewModel{
+  @Trace safeTop: number = 0
+  @Trace table: Array<string[]> = new Array(5).fill(new Array(8).fill('11'))
+  @Trace weekList: string[] = ['周一', '周二', '周三', '周四', '周五'] // ,
+
+  @Trace selectX: number = -1
+  @Trace selectY: number = -1
+
+  conText: UIContext
+  // 控制按钮
+  controlBtn: Array<BasicType> = [
+    {
+      text: '添加行',
+      click: () => {
+        this._onAddRow()
+      }
+    },
+    {
+      text: '添加列',
+      click: () => {
+        this._onAddColumn()
+      }
+    },
+    {
+      text: '删除行',
+      click: () => {
+        this._onDeleteRow()
+      }
+    },
+    {
+      text: '删除列',
+      click: () => {
+        this._onDeleteColumn()
+      }
+    },
+    {
+      text: '撤销',
+      click: () => {
+        this._onUndo()
+      }
+    },
+    {
+      text: '保存',
+      click: () => {
+        this._onSave()
+      }
+    },
+    {
+      text: '返回',
+      click: () => {
+        this._onBackPressed()
+      }
+    }
+  ]
+  // 上次的操作是不是保存
+  isSave: boolean = false
+  // 撤销数据
+  undoDataList: Array<undoData> = []
+  // 原始的周数据
+  originWeekList: string[] = ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
+
+  constructor(conText: UIContext) {
+    this.safeTop = AppStorage.get(YTAvoid.SAFE_TOP_KEY) as number
+    this.conText = conText
+
+    window.getLastWindow(this.conText.getHostContext())
+      .then(res => {
+        res.setPreferredOrientation(window.Orientation.LANDSCAPE)
+      })
+  }
+
+  // 获得焦点
+  onFocus(x: number, y: number) {
+    this.selectX = x
+    this.selectY = y
+  }
+
+
+  /**
+   * 添加行
+   */
+  _onAddRow() {
+    let x = this.table.length
+    let y = this.table[0].length
+
+    if(x == 7) return
+
+    this._addUndoData()
+    this.weekList.push(this.originWeekList[this.weekList.length])
+    this.table.push(new Array(y).fill(''))
+  }
+
+  /**
+   * 添加列
+   */
+  _onAddColumn() {
+    let x = this.table.length
+    let y = this.table[0].length
+    this._addUndoData()
+    for (let index = 0; index < this.table.length; index++) {
+      let arr = [...this.table[index]]
+      arr.push('')
+      this.table[index] = arr
+    }
+  }
+
+  /**
+   * 删除行
+   */
+  _onDeleteRow() {
+    let x = this.table.length
+    let y = this.table[0].length
+
+    if(x == 5){
+      IBestToast.show('行数不足5行')
+      return
+    }
+    this._addUndoData()
+    this.weekList.pop()
+    this.table.splice(x-1, 1)
+  }
+
+  /**
+   * 删除列
+   */
+  _onDeleteColumn() {
+    let x = this.table.length
+    let y = this.table[0].length
+
+    if(y == 8) {
+      IBestToast.show('列数不足8列')
+      return
+    }
+    this._addUndoData()
+    for (let index = 0; index < x; index++) {
+      let arr = [...this.table[index]]
+      arr.splice(y-1, 1)
+      this.table[index] = arr
+    }
+  }
+
+
+  /**
+   *  撤销操作
+   */
+  _onUndo() {
+    if(this.undoDataList.length == 0) return
+
+    const undoDate: undoData = this.undoDataList.pop()!
+    this.table = [...undoDate.arr2!]
+    this.weekList = [...undoDate.arr!]
+  }
+
+  /**
+   * 保存
+   */
+  _onSave() {
+    if(this.isSave) return
+    this.isSave = true
+  }
+
+  /**
+   * 添加 《撤销》 数据 - 仅保存 10 次操作
+   */
+  _addUndoData() {
+    this.isSave = false
+    const undoData: undoData = {
+      arr2: [...this.table],
+      arr: [...this.weekList],
+    }
+    this.undoDataList.push(undoData)
+    if(this.undoDataList.length == 10) {
+      this.undoDataList.splice(0, 1)
+    }
+  }
+
+  /**
+   * 回正屏幕并返回
+   */
+  _onBack(){
+    window.getLastWindow(this.conText.getHostContext())
+      .then(res => {
+        res.setPreferredOrientation(window.Orientation.UNSPECIFIED)
+        yTRouter.pop('')
+      })
+  }
+
+  /**
+   * 重写的返回逻辑
+   * @returns
+   */
+  _onBackPressed(){
+    if(!this.isSave) {
+      yTRouter.router2DoubleConfirmDiaLog({
+        text: '编辑内容未保存,确定离开吗?',
+        color: '#7186F9'
+      }, (res) => {
+        if(res && res.result == 'true') {
+          this._onBack()
+        }
+      })
+    } else {
+      this._onBack()
+    }
+    return true;
+  }
+}

+ 5 - 3
features/feature/src/main/ets/viewModel/FiveViewModel.ets

@@ -14,14 +14,16 @@ export class FiveViewModel{
 
   // 查看课程表
   _onLookScore() {
+    iRouter.router2CourseTablePage({ name: '课程表1' }, () => {
 
+    })
   }
 
   // 添加课程表
   _onAddTestScore() {
     iRouter.router2InputDialog({
       color: '#5668FC',
-      message: '请输入课程表',
+      message: '请输入课程表名称',
       text: '添加课程表',
       generics: {
         type: 0
@@ -29,7 +31,7 @@ export class FiveViewModel{
     }, (res) => {
       let ans = res.result as InputDiaLogParams
       if(ans) {
-        iRouter.router2IncreaseScorePage({ name: '课程表1' }, () => {
+        iRouter.router2CourseTablePage({ name: '课程表1' }, () => {
 
         })
       }
@@ -52,7 +54,7 @@ export class FiveViewModel{
   _onEditTestScore() {
     iRouter.router2InputDialog({
       color: '#5668FC',
-      message: '请输入课程表',
+      message: '请输入课程表名称',
       text: '编辑课程表',
       generics: {
         name: '课程表1',

+ 4 - 4
features/feature/src/main/ets/viewModel/FourViewModel.ets

@@ -23,8 +23,8 @@ export class FourViewModel{
   _onAddTestScore() {
     iRouter.router2InputDialog({
       color: '#5668FC',
-      message: '请输入成绩单',
-      text: '添加座位表',
+      message: '请输入考试名称',
+      text: '添加考试',
       generics: {
         type: 0
       }
@@ -54,8 +54,8 @@ export class FourViewModel{
   _onEditTestScore() {
     iRouter.router2InputDialog({
       color: '#5668FC',
-      message: '请输入成绩单',
-      text: '编辑座位表',
+      message: '请输入考试名称',
+      text: '编辑考试',
       generics: {
         name: '座位表1',
         startDate: '2025.11.10',

+ 12 - 5
features/feature/src/main/ets/viewModel/IncreaseStudentPage.ets

@@ -1,6 +1,7 @@
-import { YTAvoid, yTRouter } from "basic"
+import { IBestToast, YTAvoid, yTRouter } from "basic"
 import { iRouter } from "../utils/RouterUtils"
 
+// 添加学生信息页面
 @ObservedV2
 export class IncreaseStudentPageViewModel{
   @Trace safeTop: number = 0
@@ -19,7 +20,6 @@ export class IncreaseStudentPageViewModel{
     }
   }
 
-
   // 修改性别
   _onGenderChange(gender: string){
     this.gender = gender
@@ -27,7 +27,14 @@ export class IncreaseStudentPageViewModel{
 
   // 保存
   _onSave(){
-
+    if(!this.name) {
+      IBestToast.show('请输入姓名')
+      return
+    }
+    if(!this.gender) {
+      IBestToast.show('请选择性别')
+      return
+    }
 
     this._onBackPressed(false)
   }
@@ -37,13 +44,13 @@ export class IncreaseStudentPageViewModel{
    * @returns
    */
   _onBackPressed(isDiaLog: boolean = true){
-    if(isDiaLog){
+    if(isDiaLog && (this.name || this.gender)){
       yTRouter.router2DoubleConfirmDiaLog({
         text: '编辑内容未保存,确定离开吗?',
         color: '#7186F9'
       }, (res) => {
         if (res?.result && res.result == 'true') {
-          yTRouter.pop('')
+          yTRouter.pop()
         }
       })
     } else {

+ 275 - 8
features/feature/src/main/ets/viewModel/SeatingPlanPageViewModel.ets

@@ -1,15 +1,77 @@
-import { YTAvoid, yTRouter } from "basic"
+import { BasicType, IBestToast, YTAvoid, yTRouter } from "basic"
 import { window } from "@kit.ArkUI"
+import { unifiedDataChannel } from "@kit.ArkData"
+import { BusinessError } from "@kit.BasicServicesKit"
+import { undoData } from "../model/Index"
 
 @ObservedV2
 export class SeatingPlanPageViewModel{
   @Trace safeTop: number = 0
   // 学生列表
-  @Trace dataSource: number[] = [0, 1, 1, 0, 0, 1, 0, 1, 0, 0]
-  // 座位表
-  @Trace seatingPlan: string[][] = []
+  @Trace dataSource: string[] = ['张三', '李四', '王五', '赵六', '孙七', '周八', '吴九', '小十', '张三', '李四', '王五', '赵六', '孙七', '周八', '吴九', '小十']
+  // 座位表 - 二维数组
+  @Trace seatingPlan: Array<string[]> = new Array(4).fill(new Array(8).fill(''))
+  // 当前显示的模式: 1 - 单列  2 - 双列
+  @Trace columnMode: number = 1
 
+  // 是否保存了 - 上次的操作是否为保存
+  isSave: boolean = false
+  // 撤销数据
+  undoDataList: Array<undoData> = []
+  // 上下文对象
   conText: UIContext
+  // 控制按钮
+  controlBtn: Array<BasicType> = [
+    {
+      text: '添加行',
+      click: () => {
+        this._onAddRow()
+      }
+    },
+    {
+      text: '添加列',
+      click: () => {
+        this._onAddColumn()
+      }
+    },
+    {
+      text: '删除行',
+      click: () => {
+        this._onDeleteRow()
+      }
+    },
+    {
+      text: '删除列',
+      click: () => {
+        this._onDeleteColumn()
+      }
+    },
+    {
+      text: '单、双列切换',
+      message: 'hh',
+      click: () => {
+        this._onChangeColumn()
+      }
+    },
+    {
+      text: '撤销操作',
+      click: () => {
+        this._onUndo()
+      }
+    },
+    {
+      text: '保存',
+      click: () => {
+        this._onSave()
+      }
+    },
+    {
+      text: '返回',
+      click: () => {
+        this._onBackPressed()
+      }
+    }
+  ]
 
   constructor(conText: UIContext) {
     this.safeTop = AppStorage.get(YTAvoid.SAFE_TOP_KEY) as number
@@ -21,18 +83,223 @@ export class SeatingPlanPageViewModel{
       })
   }
 
-  
+  /**
+   * 点击学生
+   * @param seating 当前维度的学生列表
+   * @param x
+   * @param y
+   */
+  _onStudentClick(seating: string[], x : number, y : number){
+    if(!seating[y]) return
+
+    let arr = [...seating]
+    let i = arr[y]
+    arr[y] = ''
+
+    this._addUndoData()
+    this.dataSource.push(i)
+    this.seatingPlan.splice(x, 1, arr)
+  }
 
   /**
-   * 重写的返回逻辑
-   * @returns
+   * 接收方 - 接收到拖拽的学生
+   * @param seat 当前维度的学生列表
+   * @param x
+   * @param y
+   * @param value 接收到的学生
    */
-  _onBackPressed(){
+  _onDrag(seat: string[], x : number, y : number, value: string){
+    let arr = [...seat]
+
+    // 原位置上有学生
+    if(arr[y]) {
+      animateToImmediately({
+        duration: 300
+      } ,() => {
+        this.dataSource.push(arr[y])
+      })
+    }
+
+    arr[y] = value
+    this.seatingPlan[x] = arr
+  }
+
+  /**
+   * 数据源 - 拖拽事件结束
+   * @param event 拖拽事件传递的事件参数
+   */
+  _onDragEnd(event: DragEvent){
+    // onDragEnd里取到的result值在接收方onDrop设置
+    if (event.getResult() === DragResult.DRAG_SUCCESSFUL) {
+
+    } else if (event.getResult() === DragResult.DRAG_FAILED) {
+      // 拖拽结束 - 没有在接收方接收到数据, 撤销本次操作
+      this._onUndo()
+    }
+  }
+
+  /**
+   * 添加行
+   */
+  _onAddRow() {
+    let x = this.seatingPlan.length
+    let y = this.seatingPlan[0].length
+
+    this._addUndoData()
+    this.seatingPlan.push(new Array(y).fill(''))
+  }
+
+  /**
+   * 添加列
+   */
+  _onAddColumn() {
+    let x = this.seatingPlan.length
+    let y = this.seatingPlan[0].length
+    this._addUndoData()
+    for (let index = 0; index < this.seatingPlan.length; index++) {
+      let arr = [...this.seatingPlan[index]]
+      arr.push('')
+      if(this.columnMode == 2) arr.push('')
+      this.seatingPlan[index] = arr
+    }
+  }
+
+  /**
+   * 删除行
+   */
+  _onDeleteRow() {
+    let x = this.seatingPlan.length
+    let y = this.seatingPlan[0].length
+
+    if(x == 1){
+      IBestToast.show('行数不足1行')
+      return
+    }
+    this._addUndoData()
+    this.seatingPlan.splice(x-1, 1)
+  }
+
+  /**
+   * 删除列
+   */
+  _onDeleteColumn() {
+    let x = this.seatingPlan.length
+    let y = this.seatingPlan[0].length
+
+    if(y == 1) {
+      IBestToast.show('列数不足1列')
+      return
+    }
+    this._addUndoData()
+    for (let index = 0; index < x; index++) {
+      let arr = [...this.seatingPlan[index]]
+      arr.splice(y-1, 1)
+      this.seatingPlan[index] = arr
+    }
+  }
+
+  /**
+   *  单、双列切换
+   */
+  _onChangeColumn() {
+    let x = this.seatingPlan[0].length
+    if(x%2 == 1 && this.columnMode == 1) this._onAddColumn()
+    this._addUndoData()
+    this.columnMode = this.columnMode == 1 ? 2 : 1
+  }
+
+  /**
+   *  撤销操作
+   */
+  _onUndo() {
+    if(this.undoDataList.length == 0) return
+
+    const undoDate: undoData = this.undoDataList.pop()!
+    this.seatingPlan = [...undoDate.arr2!]
+    this.dataSource = [...undoDate.arr!]
+    this.columnMode = undoDate.columnMode!
+  }
+
+  /**
+   * 保存
+   */
+  _onSave() {
+    if(this.isSave) return
+    this.isSave = true
+  }
+
+  /**
+   * 添加 《撤销》 数据 - 仅保存 10 次操作
+   */
+  _addUndoData() {
+    this.isSave = false
+    const undoData: undoData = {
+      arr2: [...this.seatingPlan],
+      columnMode: this.columnMode,
+      arr: [...this.dataSource],
+    }
+    this.undoDataList.push(undoData)
+    if(this.undoDataList.length == 10) {
+      this.undoDataList.splice(0, 1)
+    }
+  }
+
+  /**
+   * 回正屏幕并返回
+   */
+  _onBack(){
     window.getLastWindow(this.conText.getHostContext())
       .then(res => {
         res.setPreferredOrientation(window.Orientation.UNSPECIFIED)
         yTRouter.pop('')
       })
+  }
+
+  /**
+   * 重写的返回逻辑
+   * @returns
+   */
+  _onBackPressed(){
+    if(!this.isSave) {
+      yTRouter.router2DoubleConfirmDiaLog({
+        text: '编辑内容未保存,确定离开吗?',
+        color: '#7186F9'
+      }, (res) => {
+        if(res && res.result == 'true') {
+          this._onBack()
+        }
+      })
+    } else {
+      this._onBack()
+    }
     return true;
   }
+
+  /******** 拖拽事件用的方法 ***********/
+  getDataFromUdmf(event: DragEvent, callback: (data: DragEvent) => void) {
+    if (this.getDataFromUdmfRetry(event, callback)) {
+      return;
+    }
+    setTimeout(() => {
+      this.getDataFromUdmfRetry(event, callback);
+    }, 1500);
+  }
+
+  getDataFromUdmfRetry(event: DragEvent, callback: (data: DragEvent) => void) {
+    try {
+      let data: UnifiedData = event.getData();
+      if (!data) {
+        return false;
+      }
+      let records: Array<unifiedDataChannel.UnifiedRecord> = data.getRecords();
+      if (!records || records.length <= 0) {
+        return false;
+      }
+      callback(event);
+      return true;
+    } catch (e) {
+      console.error("getData failed, code = " + (e as BusinessError).code + ", message = " + (e as BusinessError).message);
+      return false;
+    }
+  }
 }

+ 4 - 0
features/feature/src/main/resources/base/profile/router_map.json

@@ -28,6 +28,10 @@
       "name": "IncreaseScorePage",
       "pageSourceFile": "src/main/ets/pages/IncreaseScorePage.ets",
       "buildFunction": "IncreaseScorePageBuilder"
+    }, {
+      "name": "ClassSchedulePage",
+      "pageSourceFile": "src/main/ets/pages/ClassSchedulePage.ets",
+      "buildFunction": "ClassSchedulePageBuilder"
     }
   ]
 }