• Vue3最佳实践 第七章 TypeScript 创建Trello 任务管理器


    | 在这里插入图片描述

      ​ 我们将探讨如何使用Vue.js从零开始创建一个类似于Trello的任务管理应用程序。如果你不熟悉Trello,它是一款非常流行的任务管理工具,允许你把任务写在卡片上,然后通过一个看板的方式来直观地管理这些任务。Trello不仅可以用于个人的任务管理,还可以作为团队协作工具,在许多公司和组织中得到了广泛的应用。我们的目标是创建一个具有Trello核心功能的应用程序,包括创建任务卡片,以及通过拖放操作来移动这些卡片,以此来改变任务的状态或优先级。

      ​ 在这里我们将使用Vue.js来实现一个简单的Trello功能。这篇文章将分为两部分。在第一部分,我们将探讨如何使用Vue.js的Draggable属性和拖动事件来实现任务的移动和类别(列)的创建。我们将详细介绍如何设置这些功能,并提供代码示例来帮助你理解。在第二部分,我们将深入讨论如何使用TypeScript和Vue的Composition API来描述我们的应用程序。可以更好的了解如何由多个组件(例如 props 和 emit)组成的应用程序中设置 TypeScript。

      ​ 我们将使用前面介绍的TypeScript知识点来完成Trello功能的实现,系统这些代码将为你理解TypeScript提供有价值的信息和实践经验。我们将尽可能清晰地解释每一步,以便你能够跟上并理解我们正在做什么。阅读完这篇文章后,你将能够创建一个功能齐全的任务管理应用程序,并有足够的知识去定制和扩展它以满足你自己的需求。如果你是一个初学者,或者你想要了解如何使用拖动进行移动处理,那么这篇文章将会是一个很好的起点。

    第一章 Vue3项目创建 1 Vue CLI 创建vue项目
    第一章 Vue3项目创建 2 使用 Webpack 5 搭建 vue项目
    第一章 Vue3项目创建 3 Vite 创建 vue项目
    第二章 Vue3 基础语法指令
    第三章 Vue Router路由器的使用
    第四章 VUE常用 UI 库 1 ( element-plus,Ant ,naiveui,ArcoDesign)
    第四章 VUE常用 UI 库 2 ( ailwind 后台框架)
    第五章 Vue 组件应用 1( Props )
    第五章 Vue 组件应用 2 ( Emit )
    第五章 Vue 组件应用 3( Slots )
    第五章 Vue 组件应用 4 ( provide 和 inject )
    第五章 Vue 组件应用 5 (Vue 插件)
    第六章 Pinia,Vuex与axios,VueUse 1(Pinia)
    第六章 Pinia,Vuex与axios,VueUse 2(Vuex)
    第六章 Pinia,Vuex与axios,VueUse 3(VueUse)
    第六章 Pinia,Vuex与axios,VueUse 4(axios)
    第七章 TypeScript 上
    第七章 TypeScript 中
    第七章 TypeScript 下 创建Trello 任务管理器
    第八章 ESLint 与 测试 ( ESLint )
    第八章 ESLint 与 测试 ( Jest )
    第八章 ESLint 与 测试 (TypeScript 中Jest与检测环境集成)

    1 导入UI样式

    npm init vite@latest zht-trello
    cd zht-trello
    npm install
    npm run dev
    npm install --save-dev @arco-design/web-vue
    
    • 1
    • 2
    • 3
    • 4
    • 5

    我们使用字节的UIarco-design来作为这个例子的主体架构。

    2 创建任务数据

    在src文件夹下创建文件夹data,在data中创建两个文件,创建class.json和tasks.json文件。

    class.json

    [
      {
        "id": 1,
        "name": "看板任务一",
        "collapsed": false
      },
      {
        "id": 2,
        "name": "看板任务二",
        "collapsed": false
      },
      {
        "id": 3,
        "name": "看板任务二",
        "collapsed": false
      }
    ]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    tasks.json

    [
        {
          "id": 1,
          "category_id": 1,
          "name": "任务1",
          "start_date": "2022-12-18",
          "end_date": "2022-12-20",
          "incharge_user": "张童",
          "percentage": 100
        },
        {
          "id": 2,
          "category_id": 1,
          "name": "任务1",
          "start_date": "2020-12-19",
          "end_date": "2020-12-23",
          "incharge_user": "王鑫",
          "percentage": 90
        },
        {
          "id": 3,
          "category_id": 3,
          "name": "任务3",
          "start_date": "2022-12-19",
          "end_date": "2022-12-21",
          "incharge_user": "王鑫",
          "percentage": 40
        },
        {
          "id": 4,
          "category_id": 2,
          "name": "任务4",
          "start_date": "2022-12-21",
          "end_date": "2022-12-30",
          "incharge_user": "李佳",
          "percentage": 60
        },
        {
          "id": 5,
          "category_id": 2,
          "name": "任务5",
          "start_date": "2022-12-20",
          "end_date": "2022-12-22",
          "incharge_user": "王芳",
          "percentage": 5
        },
        {
          "id": 6,
          "category_id": 1,
          "name": "任务6",
          "start_date": "2022-12-28",
          "end_date": "2022-12-08",
          "incharge_user": "王芳",
          "percentage": 0
        }
      ]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56

      ​ 使用 ref 函数将class.json与tasks.json保存成反应数据,方便在后边的代码中使用。

    <script setup lang="ts">
    import class_data from "./data/class.json";
    import task_data from "./data/tasks.json";
    import { ref } from "vue";
    const classes = ref(class_data);
    const tasks = ref(task_data);
    script>
    <template>
    template>
    <style scoped>
    style>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    3 ref 函数的类型设置

      ​ 现在我们使用 TypeScript 为 ref 函数来设置类型。尽管代码会自动执行 TypeScript 的类型推断功能,即使没有显式设置类型,也不会显示错误消息,但是我们仍然建议尽可能地显式声明类型,以提高代码的可读性和可维护性。

      ​ 首先,我们需要创建一个用于管理应用程序中使用的类型的文件夹。在 src 文件夹中创建一个名为 types 的新文件夹。由于我们要创建的应用程序规模较小,因此我们将所有类型都存储在一个名为 index.ts 的文件中,并将它们导出以供我们的组件使用。

      ​ 应用程序中有两种主要的类型:类别 (CateClass) 和任务 (Task)。这些类型是基于固定 JSON 文件数据定义的。在类型定义中我们也可以使用 Type 而不是 Interface。

    export interface CateClass {
        id: number;
        name: string;
        collapsed?: boolean;
      }
      
      export interface Task {
        id: number;
        category_id: number;
        name: string;
        start_date: string;
        end_date: string;
        incharge_user: string;
        percentage: number;
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

      ​ 请注意,标有 ? 的属性是可选的,因此即使缺少该属性也不会发生错误。

      ​ 现在我们已经定义了类型,接下来我们需要在 App.vue 文件中导入这些类型,并使用 ref 函数来设置它们。这里是如何做到这一点的:

    <script setup lang="ts">
    import type {CateClass, Task } from "./data/tasksIndex";
    import class_data from "./data/class.json";
    import task_data from "./data/tasks.json";
    import { ref } from "vue";
     // 使用 ref 函数和我们定义的类型来设置 classes 和 tasks 的类型
    const classes = ref<CateClass[]>(class_data);
    const tasks = ref<Task[]>(task_data);
    script>
    <template>
    template>
    <style scoped>
    style>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

      ​ 在上面的代码中,ref 函数用于创建一个响应式的引用,其值可以被改变。我们使用 来显式地指定 classestasks 的类型,这样就可以确保它们总是包含正确类型的数据。

    4 设置计算属性

      ​ 创建了一个名为 renderCategoryTask 的计算属性函数。该计算属性通过遍历 classes 数组,并根据每个类别的 id 属性筛选出对应的任务,生成一个新的数组。这样,我们就得到了一个包含类别和对应任务的嵌套数据结构。就可以在模板中使用这个计算属性来渲染类别和任务组件。

      ​ 在模板中我们使用 v-for 指令遍历 renderCategoryTask 数组,并将每个类别渲染为一个 a-col 组件。在 a-card 组件中,我们显示了类别的名称,并使用 v-if 条件指令来判断是否有任务,如果有则渲染任务列表。

    <script setup lang="ts">
    import type {CateClass, Task,CateClassTask } from "./data/tasksIndex";
    import class_data from "./data/class.json";
    import task_data from "./data/tasks.json";
    import { ref, computed } from "vue";
    const classes = ref<CateClass[]>(class_data);
    const tasks = ref<Task[]>(task_data);
    const renderCategoryTask = (() => {
      return classes.value.map((classes) => {
        const filterTasks = tasks.value.filter(
          (task) => task.category_id === classes.id
        );
        return {
          id: classes.id,
          name: classes.name,
          tasks: filterTasks,
        };
      });
    });
    </script>
    <template>
    <div
        :style="{
          boxSizing: 'border-box',
          width: '100%',
          padding: '40px',
          backgroundColor: 'var(--color-fill-2)',
        }"
      >
      <h1 >Trello 任务管理</h1>
        <a-row :gutter="20" :style="{ marginBottom: '20px' }">
          <a-col :span="8" v-for="category in renderCategoryTask" :key="category.id">
            <a-card :title="category.name" 
              :bordered="false" 
              :style="{ width: '100%',height:'300px'}">
              <template #extra>
                <a-link>详细</a-link>
              </template>
              <a-list  v-for="task in category.tasks" :key="task.id">
                <a-list-item>{{task.name}} ||  {{task.incharge_user}}</a-list-item>
              </a-list>
            </a-card>
          </a-col>
        </a-row>
    </div>
    </template>
    <style scoped>
    </style>      
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    5 组件设置

      ​ 现在我们要将类别和任务的描述组件化。通过将其做成一个组件,大家可以加深对使用 TypeScript 时,通过 emit 来设置 props 和事件设置的理解。

      ​ 在 CategoryItem.vue 文件中,我们使用 defineProps 来设置从 props 传递过来的 categoryTask。defineProps 不需要执行 import。defineProps 指定使用泛型传递的 props 类型。这里的 CategoryTask 是类型。

    <template>
      <a-col :span="8">
        <a-card :title="category?.name" :bordered="false" :style="{ width: '300px' }">
          <template #extra>
            <a-link>详细a-link>
          template>
          <a-list v-for="task in category.tasks" :key="task.id">
            <TaskItem :task="task" draggable="true">TaskItem>
          a-list>
        a-card>
      a-col>
    template>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    CategoryItem.vue

      ​ 首先,在组件文件夹中创建一个 CategoryItem.vue 文件。起初,只有 script 和 template 标签没有描述任何处理。

    <script setup lang="ts">
    import type { CateClassTask } from "../data/tasksIndex";
    import TaskItem from "./TaskItem.vue";
    interface Props {
        category: CateClassTask;
    }
    defineProps<Props>();
    </script>
    <template>
    <a-col :span="8"  >
            <a-card :title="category?.name" 
              :bordered="false" 
              :style="{ width: '100%',height:'300px'}">
              <template #extra>
                <a-link>详细</a-link>
              </template>
              <a-list  v-for="task in category.tasks" :key="task.id" >
              <TaskItem :task="task"  draggable="true"/>
              </a-list>
            </a-card>
          </a-col>
    </template>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    ​ 此外,你还可以创建 TaskItem 组件来显示任务。对于 TaskItem 组件,描述如下,这样你就可以在 props 中接收任务。

    TaskItem.vue

    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

      ​ 在 CategoryItem.vue 文件中导入创建的 TaskItem 组件。在模板标签中使用导入的 TaskItem 组件。对于 props,在 TaskItem.vue 文件中的 props 任务集中传递使用 v-for 指令展开的任务。

    6 可拖动设置

      ​ 我们将实现同一类别(列)内的任务移动,我们需要将任务元素设置为可拖动。首先在CategoryItem.vue代码中,添加 draggable 属性并将其设置为 true 以使元素可拖动。设置一个setDragTask拖动事件,通过这个单击事件来抓取拖动组件中的元素,并在按下鼠标按钮的同时移动元素。

    
    <script setup lang="ts">
    //设置移动事件
    const setDragTask = () => {
      console.log('drag');
    };
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

      ​ 在dragstart事件中设置setDragTask方法并检查操作。v-on指令可用于配置事件,这里使用缩写@。当在浏览器中移动组件的时候,在开发者工具控制台中会显示字符串“drag”。

    7 移动被拖动元素

    1 在CategoryItem.vue组件中设置dragover事件。在dragover事件中调用dragOverTask函数,并将task作为参数传递进去。

    <CategoryItem
      class="min-w-[400px]"
      v-for="categoryTask in renderCategoryTask"
      :key="categoryTask.id"
      :categoryTask="categoryTask"
      @setDragTask="setDragTask"
      @dragOverTask="dragOverTask"
    />
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    2 CategoryItem.vue脚本模块里在emit中,将事件名称设置为dragOverTask,并像之前的dragstart事件一样使用emit将任务传递给父组件。

    const emit = defineEmits<{
      (e: "setDragTask", task: Task): void;
      (e: "dragOverTask", task: Task): void;
    }>();
    
    const setDragTask = (task: Task) => {
      emit("setDragTask", task);
    };
    
    const dragOverTask = (task: Task) => {
      emit("dragOverTask", task);
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    3 在dragOverTask函数中,检查传递过来的overTask信息以及之前设置的dragTask内容,以进行操作

    const dragOverTask = (overTask: Task) => {
      console.log('task:', dragTask.value);
      console.log('overTask:', overTask);
    };
    
    • 1
    • 2
    • 3
    • 4

    4 在检查操作时,只有当dragTask的id和overTask的id不同时才执行移动操作。注意,我们在dragTask.value后面添加了?,因为dragTask的值可能为null,如果不添加?,将会显示错误消息。

    const dragOverTask = (overTask: Task) => {
      if (dragTask.value?.id !== overTask.id) {
        const deleteIndex = tasks.value.findIndex(
          (task) => task.id === dragTask.value?.id
        );
        const addIndex = tasks.value.findIndex((task) => task.id === overTask.id);
        if (dragTask.value !== null) {
          tasks.value.splice(deleteIndex, 1);
          tasks.value.splice(addIndex, 0, dragTask.value);
        }
      }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    8 trello 代码

    trello项目结构

    zht-trello
       |---node_modules
       |---public
       |    |--index.html    // 项目启动页面
       |---src               // 代码源文件
       |    |--assets        // 资源目录
       |    |--components    // 组件目录
       |    |   |-- CategoryItem.vue   // 看板程序
       |    |   |-- TaskItem.vue       // 任务条
       |    |   |-- data
       |    |   |	|-- class.json    //
       |    |   |	|-- tasks.json   
       |    |   |	|-- tasksIndex.ts   
       |    |--main.js       // 入口文件
       |    |--App.vue       // 主程序
       |----package.json
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    main.ts

    import { createApp } from 'vue'
    import App from './App.vue'
    import ArcoVue from '@arco-design/web-vue';
    import '@arco-design/web-vue/dist/arco.css';
    const app = createApp(App)
    app.use(ArcoVue);
    app.mount('#app')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    App.vue

    <script setup lang="ts">
    import CategoryItem from "./components/CategoryItem.vue";
    import type {CateClass, Task,CateClassTask } from "./data/tasksIndex";
    import class_data from "./data/class.json";
    import task_data from "./data/tasks.json";
    import { ref, computed } from "vue";
    const classes = ref<CateClass[]>(class_data);
    const tasks = ref<Task[]>(task_data);
    const dragTask = ref<Task | null>(null);
    const renderCategoryTask = computed(() => {
      return classes.value.map((classes) => {
        const filterTasks:Task[] = tasks.value.filter(
          (task) => task.category_id === classes.id
        );
        return {
          id: classes.id,
          name: classes.name,
          tasks: filterTasks,
          collapsed:classes.collapsed
        } as CateClassTask;
      })
    });
    const setDragTask = (task: Task) => {
      dragTask.value = task;
    };
    
    const dragOverTask = (overTask: Task) => {
      if (dragTask.value?.id !== overTask.id) {
        const deleteIndex = tasks.value.findIndex(
          (task) => task.id === dragTask.value?.id
        );
        const addIndex = tasks.value.findIndex((task) => task.id === overTask.id);
        if (dragTask.value !== null) {
          tasks.value.splice(deleteIndex, 1);
          dragTask.value.category_id = overTask.category_id; //追加
          tasks.value.splice(addIndex, 0, dragTask.value);
        }
      }
    };
    
    const dragOverCategory = (categoryTask: CateClassTask) => {
      if (dragTask.value?.category_id !== categoryTask.id) {
        const filterTasks = tasks.value.filter(
          (task) => task.category_id === categoryTask.id
        );
        if (filterTasks.length === 0 && dragTask.value !== null)
          dragTask.value.category_id = categoryTask.id;
      }
    };
    </script>
    <template>
    <div
        :style="{
          boxSizing: 'border-box',
          width: '100%',
          padding: '40px',
          backgroundColor: 'var(--color-fill-2)',
        }"
      >
      <h1 >Trello 任务管理</h1>
        <a-row :gutter="20" :style="{ marginBottom: '20px' }">
          <CategoryItem
            v-for="category in renderCategoryTask"
            :key="category.id"
            :category="category"
            @dragover="dragOverCategory(category)"
            @setDragTask="setDragTask"
            @dragOverTask="dragOverTask"
          />
        </a-row>
    </div>
    </template>
    <style scoped>
    </style>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74

    CategoryItem.vue

    <script setup lang="ts">
    import type { Task,CateClassTask } from "../data/tasksIndex";
    import TaskItem from "./TaskItem.vue";
    interface Props {
        category: CateClassTask;
    }
    defineProps<Props>();
    const emit = defineEmits<{
      (e: "setDragTask", task: Task): void;
      (e: "dragOverTask", task: Task): void;
    }>();
    
    const setDragTask = (task: Task) => {
      emit("setDragTask", task);
    };
    
    const dragOverTask = (task: Task) => {
      emit("dragOverTask", task);
    };
    </script>
    <template>
    <a-col :span="8"  >
            <a-card :title="category?.name" 
              :bordered="false" 
              :style="{ width: '100%',height:'300px'}"
              >
              <template #extra>
                <a-link>详细</a-link>
              </template>
              <a-list  v-for="task in category.tasks" :key="task.id" >
              <TaskItem :task="task"  
                draggable="true"
                @dragstart="setDragTask(task)"
                @dragover="dragOverTask(task)"
               />
              </a-list>
            </a-card>
          </a-col>
    </template>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    TaskItem.vue

    <script setup lang="ts">
    import type { Task } from "../data/tasksIndex";
    defineProps<{
      task: Task;
    }>();
    </script>
    <template>
        <a-list-item>{{task.name}} ||  {{task.incharge_user}}</a-list-item>
    <emplate>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    tasksIndex.ts

    export interface CateClass {
        id: number;
        name: string;
        collapsed?: boolean;
      }
      export interface Task {
        id: number;
        category_id: number;
        name: string;
        start_date: string;
        end_date: string;
        incharge_user: string;
        percentage: number;
      }
      export interface CateClassTask {
        id: number;
        name: string;
        collapsed?: boolean;
        tasks: Task[];
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
  • 相关阅读:
    底层概念的重要意义
    飞书与企业微信的异同
    众和策略:几点开盘和收盘股票?
    nginx的重定向(rewrite)
    使用Java将PPT、PPTX和PDF转换为图片
    优质快刊合集!内含TOP刊、CCF推荐期刊!编辑友好,极速发表!
    VMware打开共享虚拟机后找不到/mnt/hgfs/文件夹,以及不能拖拽/复制粘贴等操作,ubuntu不能安装VMware tools
    安科瑞电气防火限流式保护器在小型人员密集场所的应用
    15、IOC 之ApplicationContext 的附加功能
    C/C++通过位操作实现2个uint32_t合并为uint64_t
  • 原文地址:https://blog.csdn.net/zhtbs/article/details/133632739