libf 2 vuotta sitten
vanhempi
commit
93ab9520b5
39 muutettua tiedostoa jossa 60421 lisäystä ja 0 poistoa
  1. 85 0
      src/App.vue
  2. 20 0
      src/components/box/Box.vue
  3. 19 0
      src/components/box/FlexBox.vue
  4. 17 0
      src/components/box/HCBBox.vue
  5. 17 0
      src/components/box/HCMBox.vue
  6. 17 0
      src/components/box/HCTBox.vue
  7. 17 0
      src/components/box/HLBBox.vue
  8. 17 0
      src/components/box/HLMBox.vue
  9. 17 0
      src/components/box/HLTBox.vue
  10. 18 0
      src/components/box/HRBBox.vue
  11. 18 0
      src/components/box/HRMBox.vue
  12. 18 0
      src/components/box/HRTBox.vue
  13. 17 0
      src/components/box/VCBBox.vue
  14. 17 0
      src/components/box/VCMBox.vue
  15. 17 0
      src/components/box/VCTBox.vue
  16. 17 0
      src/components/box/VLBBox.vue
  17. 17 0
      src/components/box/VLMBox.vue
  18. 17 0
      src/components/box/VLTBox.vue
  19. 17 0
      src/components/box/VRBBox.vue
  20. 17 0
      src/components/box/VRMBox.vue
  21. 17 0
      src/components/box/VRTBox.vue
  22. 127 0
      src/components/calendar/Calendar.js
  23. 290 0
      src/components/calendar/Calendar.vue
  24. 23 0
      src/components/calendar/calendar.css
  25. 99 0
      src/components/calendar/mock-data.js
  26. 67 0
      src/components/calendar/theme.js
  27. 1123 0
      src/components/calendar/tuical/toastui-calendar.css
  28. 27296 0
      src/components/calendar/tuical/toastui-calendar.ie11.js
  29. 9 0
      src/components/calendar/tuical/toastui-calendar.ie11.min.js
  30. 19597 0
      src/components/calendar/tuical/toastui-calendar.js
  31. 6 0
      src/components/calendar/tuical/toastui-calendar.min.css
  32. 9 0
      src/components/calendar/tuical/toastui-calendar.min.js
  33. 10598 0
      src/components/calendar/tuical/toastui-calendar.mjs
  34. 26 0
      src/components/calendar/utils.js
  35. 102 0
      src/components/layout/Footer.vue
  36. 187 0
      src/components/layout/Header.vue
  37. 255 0
      src/components/layout/SideBar.vue
  38. 106 0
      src/components/message/MessageView.vue
  39. 68 0
      src/main.js

+ 85 - 0
src/App.vue

@@ -0,0 +1,85 @@
+<template>
+  <div class="m3" v-if="auth && auth.signedUser">
+    <Header :auth="auth.signedUser" class="header" v-if="control.header.show"></Header>
+    <div class="main">
+      <SideBar class="sidebar" :auth="auth.signedUser" :global="global"  v-if="control.sidebar.show"></SideBar>
+      <div class="mainview" style="width:100vw;height:100vh;display:flex;flex-flow:column nowrap;align-items:center;justify-content:center;">
+         <Calendar style="flex: 1 1 auto; margin:10px;"></Calendar>
+      </div>
+    </div>
+    <Footer :auth="auth.signedUser" class="footer" v-if="control.footer.show"></Footer>
+  </div>
+</template>
+
+<script>
+
+import Header from './components/layout/Header';
+import Footer from './components/layout/Footer';
+import SideBar from './components/layout/SideBar';
+import Calendar from './components/calendar/Calendar.vue';
+
+export default {
+  name: 'app',
+  components: {
+    Header,
+    Footer,
+    SideBar,
+    Calendar
+  },
+  data(){
+    return {
+      global: null,
+      auth: null,
+      control:{
+        header:{
+          show: true,
+        },
+        sidebar:{
+          show: false,
+        },
+        footer:{
+          show: true,
+        }
+      }
+    }
+  },
+  created(){
+    this.global = window.m3.global;
+    this.auth = window.m3.auth;
+  },
+  methods: {
+    open_calendar() {
+      Calendar.show();
+    }
+  },
+}
+</script>
+
+<style>
+  body{
+    font-size: 12px;
+    font-family: "PingFang SC",Arial,"Microsoft YaHei",sans-serif;
+    margin: 0px;
+    padding: 0px;
+    overflow: hidden;
+  }
+  
+  .el-input__inner{
+    padding: 0 5px!important;
+  }
+
+  .el-menu .svg-icon{
+    width: 1.2em!important;
+    height: 1.2em!important;
+    padding: 0px 5px 0 0;
+  }
+
+  .main{
+    padding-top: 50px;
+    display: flex;
+  }
+
+  .container{
+    overflow: hidden;
+  }
+</style>

+ 20 - 0
src/components/box/Box.vue

@@ -0,0 +1,20 @@
+<template>
+    <div class="ovo-box">
+        <slot></slot>
+    </div>
+</template>
+<style>
+.ovo-box {
+	order: 0;
+	flex: 1 1 auto;
+	align-self: auto;
+	position: static;
+	display: block;
+	border: 0px solid transparent;
+	padding: 0px 0px 0px 0px;
+	margin: 0px 0px 0px 0px;
+	width: 100%;
+	height: 100%;
+	overflow: visible;
+}
+</style>

+ 19 - 0
src/components/box/FlexBox.vue

@@ -0,0 +1,19 @@
+<template>
+    <Box class="ovo-flex-box">
+        <slot></slot>
+    </Box>
+</template>
+<script>
+import Box from './Box.vue'
+export default {components: { Box }}
+</script>
+<style>
+.ovo-flex-box {
+	position: relative;
+	display: flex;
+	flex-flow: row nowrap;
+	justify-content: flex-start;
+	align-items: flex-start;
+	align-content: flex-start;
+}
+</style>

+ 17 - 0
src/components/box/HCBBox.vue

@@ -0,0 +1,17 @@
+<template>
+    <FlexBox class="ovo-hcb-box">
+        <slot></slot>
+	</FlexBox>
+</template>
+<script>
+import FlexBox from './FlexBox.vue'
+export default {components: { FlexBox }}
+</script>
+<style>
+/* items horizontal - align center bottom */
+.ovo-hcb-box {
+	flex-flow: row;
+	justify-content: center;
+	align-items: flex-end;
+}
+</style>

+ 17 - 0
src/components/box/HCMBox.vue

@@ -0,0 +1,17 @@
+<template>
+    <FlexBox class="ovo-hcm-box">
+        <slot></slot>
+	</FlexBox>
+</template>
+<script>
+import FlexBox from './FlexBox.vue'
+export default {components: { FlexBox }}
+</script>
+<style>
+/* items horizontal - align center middle*/
+.ovo-hcm-box {
+	flex-flow: row;
+	justify-content: center;
+	align-items: center;
+}
+</style>

+ 17 - 0
src/components/box/HCTBox.vue

@@ -0,0 +1,17 @@
+<template>
+    <FlexBox class="ovo-hct-box">
+        <slot></slot>
+	</FlexBox>
+</template>
+<script>
+import FlexBox from './FlexBox.vue'
+export default {components: { FlexBox }}
+</script>
+<style>
+/* items horizontal - align center top*/
+.ovo-hct-box {
+	flex-flow: row;
+	justify-content: center;
+	align-items: flex-start;
+}
+</style>

+ 17 - 0
src/components/box/HLBBox.vue

@@ -0,0 +1,17 @@
+<template>
+    <FlexBox class="ovo-hlb-box">
+        <slot></slot>
+	</FlexBox>
+</template>
+<script>
+import FlexBox from './FlexBox.vue'
+export default {components: { FlexBox }}
+</script>
+<style>
+/* items horizontal - align left */
+.ovo-hlb-box {
+	flex-flow: row;
+	justify-content: flex-start;
+	align-items: flex-end;
+}
+</style>

+ 17 - 0
src/components/box/HLMBox.vue

@@ -0,0 +1,17 @@
+<template>
+    <FlexBox class="ovo-hlm-box">
+        <slot></slot>
+	</FlexBox>
+</template>
+<script>
+import FlexBox from './FlexBox.vue'
+export default {components: { FlexBox }}
+</script>
+<style>
+/* items horizontal - align left */
+.ovo-hlm-box {
+	flex-flow: row;
+	justify-content: flex-start;
+	align-items: center;
+}
+</style>

+ 17 - 0
src/components/box/HLTBox.vue

@@ -0,0 +1,17 @@
+<template>
+    <FlexBox class="ovo-hlt-box">
+        <slot></slot>
+	</FlexBox>
+</template>
+<script>
+import FlexBox from './FlexBox.vue'
+export default {components: { FlexBox }}
+</script>
+<style>
+/* items horizontal - align left */
+.ovo-hlt-box {
+	flex-flow: row;
+	justify-content: flex-start;
+	align-items: flex-start;
+}
+</style>

+ 18 - 0
src/components/box/HRBBox.vue

@@ -0,0 +1,18 @@
+<template>
+    <FlexBox class="ovo-hrb-box">
+        <slot></slot>
+	</FlexBox>
+</template>
+<script>
+import FlexBox from './FlexBox.vue'
+export default {components: { FlexBox }}
+</script>
+<style>
+/* items horizontal - align right */
+.ovo-hrb-box {
+	flex-flow: row;
+	justify-content: flex-end;
+	align-items: flex-end;
+}
+
+</style>

+ 18 - 0
src/components/box/HRMBox.vue

@@ -0,0 +1,18 @@
+<template>
+    <FlexBox class="ovo-hrm-box">
+        <slot></slot>
+	</FlexBox>
+</template>
+<script>
+import FlexBox from './FlexBox.vue'
+export default {components: { FlexBox }}
+</script>
+<style>
+/* items horizontal - align right */
+.ovo-hrm-box {
+	flex-flow: row;
+	justify-content: flex-end;
+	align-items: center;
+}
+
+</style>

+ 18 - 0
src/components/box/HRTBox.vue

@@ -0,0 +1,18 @@
+<template>
+    <FlexBox class="ovo-hrt-box">
+        <slot></slot>
+	</FlexBox>
+</template>
+<script>
+import FlexBox from './FlexBox.vue'
+export default {components: { FlexBox }}
+</script>
+<style>
+/* items horizontal - align right */
+.ovo-hrt-box {
+	flex-flow: row;
+	justify-content: flex-end;
+	align-items: flex-start;
+}
+
+</style>

+ 17 - 0
src/components/box/VCBBox.vue

@@ -0,0 +1,17 @@
+<template>
+    <FlexBox class="ovo-vcb-box">
+        <slot></slot>
+	</FlexBox>
+</template>
+<script>
+import FlexBox from './FlexBox.vue'
+export default {components: { FlexBox }}
+</script>
+<style>
+/* items vertical - align middle */
+.ovo-vcb-box {
+	flex-flow: column;
+	justify-content: flex-end;
+	align-items: center;
+}
+</style>

+ 17 - 0
src/components/box/VCMBox.vue

@@ -0,0 +1,17 @@
+<template>
+    <FlexBox class="ovo-vcm-box">
+        <slot></slot>
+	</FlexBox>
+</template>
+<script>
+import FlexBox from './FlexBox.vue'
+export default {components: { FlexBox }}
+</script>
+<style>
+/* items vertical - align middle */
+.ovo-vcm-box {
+	flex-flow: column;
+	justify-content: center;
+	align-items: center;
+}
+</style>

+ 17 - 0
src/components/box/VCTBox.vue

@@ -0,0 +1,17 @@
+<template>
+    <FlexBox class="ovo-vct-box">
+        <slot></slot>
+	</FlexBox>
+</template>
+<script>
+import FlexBox from './FlexBox.vue'
+export default {components: { FlexBox }}
+</script>
+<style>
+/* items vertical - align middle */
+.ovo-vct-box {
+	flex-flow: column;
+	justify-content: flex-start;
+	align-items: center;
+}
+</style>

+ 17 - 0
src/components/box/VLBBox.vue

@@ -0,0 +1,17 @@
+<template>
+    <FlexBox class="ovo-vlb-box">
+        <slot></slot>
+	</FlexBox>
+</template>
+<script>
+import FlexBox from './FlexBox.vue'
+export default {components: { FlexBox }}
+</script>
+<style>
+/* items vertical - align top */
+.ovo-vlb-box {
+	flex-flow: column;
+	justify-content: flex-end;
+	align-items: flex-start;
+}
+</style>

+ 17 - 0
src/components/box/VLMBox.vue

@@ -0,0 +1,17 @@
+<template>
+    <FlexBox class="ovo-vlm-box">
+        <slot></slot>
+	</FlexBox>
+</template>
+<script>
+import FlexBox from './FlexBox.vue'
+export default {components: { FlexBox }}
+</script>
+<style>
+/* items vertical - align top */
+.ovo-vlm-box {
+	flex-flow: column;
+	justify-content: center;
+	align-items: flex-start;
+}
+</style>

+ 17 - 0
src/components/box/VLTBox.vue

@@ -0,0 +1,17 @@
+<template>
+    <FlexBox class="ovo-vlt-box">
+        <slot></slot>
+	</FlexBox>
+</template>
+<script>
+import FlexBox from './FlexBox.vue'
+export default {components: { FlexBox }}
+</script>
+<style>
+/* items vertical - align top */
+.ovo-vlt-box {
+	flex-flow: column;
+	justify-content: flex-start;
+	align-items: flex-start;
+}
+</style>

+ 17 - 0
src/components/box/VRBBox.vue

@@ -0,0 +1,17 @@
+<template>
+    <FlexBox class="ovo-vrb-box">
+        <slot></slot>
+	</FlexBox>
+</template>
+<script>
+import FlexBox from './FlexBox.vue'
+export default {components: { FlexBox }}
+</script>
+<style>
+/* items vertical - align bottom */
+.ovo-vrb-box {
+	flex-flow: column;
+	justify-content: flex-end;
+	align-items: flex-end;
+}
+</style>

+ 17 - 0
src/components/box/VRMBox.vue

@@ -0,0 +1,17 @@
+<template>
+    <FlexBox class="ovo-vrm-box">
+        <slot></slot>
+	</FlexBox>
+</template>
+<script>
+import FlexBox from './FlexBox.vue'
+export default {components: { FlexBox }}
+</script>
+<style>
+/* items vertical - align bottom */
+.ovo-vrm-box {
+	flex-flow: column;
+	justify-content: center;
+	align-items: flex-end;
+}
+</style>

+ 17 - 0
src/components/box/VRTBox.vue

@@ -0,0 +1,17 @@
+<template>
+    <FlexBox class="ovo-vrt-box">
+        <slot></slot>
+	</FlexBox>
+</template>
+<script>
+import FlexBox from './FlexBox.vue'
+export default {components: { FlexBox }}
+</script>
+<style>
+/* items vertical - align bottom */
+.ovo-vrt-box {
+	flex-flow: column;
+	justify-content: flex-start;
+	align-items: flex-end;
+}
+</style>

+ 127 - 0
src/components/calendar/Calendar.js

@@ -0,0 +1,127 @@
+/* eslint-disable no-undefined */
+import Calendar from './tuical/toastui-calendar';
+import Vue from 'vue';
+
+export default Vue.component('ToastUICalendar', {
+  name: 'ToastUICalendar',
+  props: {
+    view: String,
+    useFormPopup: {
+      type: Boolean,
+      default: () => undefined,
+    },
+    useDetailPopup: {
+      type: Boolean,
+      default: () => undefined,
+    },
+    isReadOnly: {
+      type: Boolean,
+      default: () => undefined,
+    },
+    usageStatistics: {
+      type: Boolean,
+      default: () => undefined,
+    },
+    eventFilter: Function,
+    week: Object,
+    month: Object,
+    gridSelection: {
+      type: [Object, Boolean],
+      default: () => undefined,
+    },
+    timezone: Object,
+    theme: Object,
+    template: Object,
+    calendars: Array,
+    notices: Array,
+    events: Array,
+  },
+  data() {
+    return {
+      calendarInstance: null,
+    };
+  },
+  watch: {
+    view(value) {
+      this.calendarInstance.changeView(value);
+    },
+    useFormPopup(value) {
+      this.calendarInstance.setOptions({ useFormPopup: value });
+    },
+    useDetailPopup(value) {
+      this.calendarInstance.setOptions({ useDetailPopup: value });
+    },
+    isReadOnly(value) {
+      this.calendarInstance.setOptions({ isReadOnly: value });
+    },
+    eventFilter(value) {
+      this.calendarInstance.setOptions({ eventFilter: value });
+    },
+    week(value) {
+      this.calendarInstance.setOptions({ week: value });
+    },
+    month(value) {
+      this.calendarInstance.setOptions({ month: value });
+    },
+    gridSelection(value) {
+      this.calendarInstance.setOptions({ gridSelection: value });
+    },
+    timezone(value) {
+      this.calendarInstance.setOptions({ timezone: value });
+    },
+    theme(value) {
+      this.calendarInstance.setTheme(value);
+    },
+    template(value) {
+      this.calendarInstance.setOptions({ template: value });
+    },
+    calendars(value) {
+      this.calendarInstance.setCalendars(value);
+    },
+    notices(value) {
+      this.calendarInstance.setNotices(value);
+    },
+    events(value) {
+      this.calendarInstance.clear();
+      this.calendarInstance.createEvents(value);
+    },
+  },
+  mounted() {
+    this.calendarInstance = new Calendar(this.$refs.container, {
+      defaultView: this.view,
+      useFormPopup: this.useFormPopup,
+      useDetailPopup: this.useDetailPopup,
+      isReadOnly: this.isReadOnly,
+      usageStatistics: this.usageStatistics,
+      eventFilter: this.eventFilter,
+      week: this.week,
+      month: this.month,
+      gridSelection: this.gridSelection,
+      timezone: this.timezone,
+      theme: this.theme,
+      template: this.template,
+      calendars: this.calendars,
+      notices: this.notices,
+    });
+    this.addEventListeners();
+    this.calendarInstance.createEvents(this.events);
+  },
+  beforeDestroy() {
+    this.calendarInstance.off();
+    this.calendarInstance.destroy();
+  },
+  methods: {
+    addEventListeners() {
+      Object.keys(this.$listeners).forEach((eventName) => {
+        this.calendarInstance.on(eventName, (...args) => this.$emit(eventName, ...args));
+      });
+    },
+    getRootElement() {
+      return this.$refs.container;
+    },
+    getInstance() {
+      return this.calendarInstance;
+    },
+  },
+  template: '<div ref="container" class="toastui-vue-calendar" />',
+});

+ 290 - 0
src/components/calendar/Calendar.vue

@@ -0,0 +1,290 @@
+<template>
+  <div>
+  <div style="
+	border: 1px solid #cccccc;
+	padding: 0px 0px 0px 0px;
+	margin: 0px 0px 0px 0px;
+  width: 1000px;
+	overflow: visible;">
+    <span style="margin-left:10px;margin-right:10px;">📅</span>
+    <select
+      v-model="selectedView"
+      class="view-select"
+    >
+      <option
+        v-for="view in viewOptions"
+        :key="view.value"
+        :value="view.value"
+      >
+        {{ view.title }}
+      </option>
+    </select>
+    <div class="buttons">
+      <button
+        type="button"
+        @click="onClickTodayButton"
+      >
+        今天
+      </button>
+      <el-button
+        type="text" 
+        icon="el-icon-d-arrow-left"
+        @click="onClickMoveButton(-1)"
+      ></el-button>
+      <span class="date-range">{{ dateRangeText }}</span>
+      <el-button
+        type="text"
+        icon="el-icon-d-arrow-right"
+        @click="onClickMoveButton(1)"
+      ></el-button>
+    </div>
+    <ToastUICalendar
+      ref="calendar"
+      style="height:500px;"
+      :view="'month'"
+      :use-form-popup="true"
+      :use-detail-popup="true"
+      :week="uioption.week"
+      :month="uioption.month"
+      :timezone="{ zones }"
+      :theme="theme"
+      :template="{
+        milestone: getTemplateForMilestone,
+        allday: getTemplateForAllday,
+        monthGridHeader: monthGridHeader,
+      }"
+      :grid-selection="true"
+      :calendars="calendars"
+      :notices="notices"
+      :events="events"
+      @selectDateTime="onSelectDateTime"
+      @beforeCreateEvent="onBeforeCreateEvent"
+      @beforeUpdateEvent="onBeforeUpdateEvent"
+      @beforeDeleteEvent="onBeforeDeleteEvent"
+      @afterRenderEvent="onAfterRenderEvent"
+      @clickDayName="onClickDayName"
+      @clickEvent="onClickEvent"
+      @clickTimezonesCollapseBtn="onClickTimezonesCollapseBtn"
+    />
+  </div>
+  </div>
+</template>
+
+<script>
+/* eslint-disable no-console */
+import ToastUICalendar from './Calendar.js';
+import './tuical/toastui-calendar.css';
+import 'tui-date-picker/dist/tui-date-picker.min.css';
+import 'tui-time-picker/dist/tui-time-picker.min.css';
+
+import { events, calendars } from './mock-data';
+import { theme } from './theme';
+import './calendar.css';
+
+export default {
+  components: {
+    ToastUICalendar,
+  },
+  data() {
+    return {
+      calendars: calendars,
+      notices: [
+        {
+          id: "n1",
+          name: "notice1"
+        },
+        {
+          id: "n2",
+          name: "notice2"
+        },
+      ],
+      events,
+      zones: [
+        {
+          timezoneName: 'Asia/Shanghai',
+          displayLabel: 'Beijing',
+          tooltip: 'UTC+08:00',
+        },
+      ],
+      theme,
+      selectedView: 'month',
+      viewOptions: [
+        {
+          title: '月',
+          value: 'month', 
+        },
+        {
+          title: '周',
+          value: 'week',
+        },
+        {
+          title: '天',
+          value: 'day',
+        },
+      ],
+      uioption: {
+        month: {
+          dayNames: ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'],
+          startDayOfWeek: 1,
+        },
+        week: {
+          dayNames: ['日', '一', '二', '三', '四', '五', '六'],
+          showTimezoneCollapseButton: true,
+          timezonesCollapsed: false,
+          eventView: true,
+          taskView: true,
+        },
+      },
+      dateRangeText: '',
+    };
+  },
+  computed: {
+    calendarInstance() {
+      return this.$refs.calendar.getInstance();
+    },
+  },
+  watch: {
+    selectedView(newView) {
+      this.calendarInstance.changeView(newView);
+      this.setDateRangeText();
+    },
+  },
+  mounted() {
+
+    m3.callFS("/matrix/calendar/load.js").then( (res)=>{
+      //console.log(res)
+      if (res.status == "ok" && res.message && res.message.data && res.message.data.length > 0) {
+        var cals = [];
+        for(var i in calendars) {
+          cals.push(calendars[i]);
+        }
+        for(var i in res.message.calendars) {
+          cals.push(res.message.calendars[i]);
+        }
+        this.calendarInstance.setCalendars(cals);
+      }
+    }).catch( (err)=>{
+      console.error(err);
+    } );
+
+    this.setDateRangeText();
+  },
+  methods: {
+    getTemplateForMilestone(event) {
+      return `<span style="color: #fff; background-color: ${event.backgroundColor};">${event.title}</span>`;
+    },
+    getTemplateForAllday(event) {
+      return `[All day] ${event.title}`;
+    },
+    monthGridHeader(model) {
+      const date = parseInt(model.date.split('-')[2], 10);
+      var cls = "toastui-calendar-weekday-grid-date";
+      if(model.isToday){
+        cls += " toastui-calendar-weekday-grid-date-decorator";
+      }
+      cls += " toastui-calendar-template-monthGridHeader";
+      return `<span class="${cls}">${date}</span>`
+    },
+    onSelectDateTime({ start, end }) {
+      console.group('onSelectDateTime');
+      console.log(`Date : ${start} ~ ${end}`);
+      console.groupEnd();
+    },
+    onBeforeCreateEvent(eventData) {
+      const event = {
+        calendarId: eventData.calendarId || '',
+        id: String(Math.random()),
+        title: eventData.title,
+        isAllday: eventData.isAllday,
+        start: eventData.start,
+        end: eventData.end,
+        category: eventData.isAllday ? 'allday' : 'time',
+        dueDateClass: '',
+        location: eventData.location,
+        state: eventData.state,
+        isPrivate: eventData.isPrivate,
+      };
+
+      this.calendarInstance.createEvents([event]);
+    },
+    onBeforeUpdateEvent(updateData) {
+      console.group('onBeforeUpdateEvent');
+      console.log(updateData);
+      console.groupEnd();
+
+      const targetEvent = updateData.event;
+      const changes = { ...updateData.changes };
+
+      this.calendarInstance.updateEvent(targetEvent.id, targetEvent.calendarId, changes);
+    },
+
+    onBeforeDeleteEvent({ title, id, calendarId }) {
+      console.group('onBeforeDeleteEvent');
+      console.log('Event Info : ', title);
+      console.groupEnd();
+
+      this.calendarInstance.deleteEvent(id, calendarId);
+    },
+    onAfterRenderEvent({ title }) {
+      console.group('onAfterRenderEvent');
+      console.log('Event Info : ', title);
+      console.groupEnd();
+    },
+    onClickDayName({ date }) {
+      console.group('onClickDayName');
+      console.log('Date : ', date);
+      console.groupEnd();
+    },
+    onClickEvent({ nativeEvent, event }) {
+      console.group('onClickEvent');
+      console.log('MouseEvent : ', nativeEvent);
+      console.log('Event Info : ', event);
+      console.groupEnd();
+    },
+    onClickTimezonesCollapseBtn(timezoneCollapsed) {
+      console.group('onClickTimezonesCollapseBtn');
+      console.log('Is Timezone Collapsed?: ', timezoneCollapsed);
+      console.groupEnd();
+
+      const newTheme = {
+        'week.daygridLeft.width': '100px',
+        'week.timegridLeft.width': '100px',
+      };
+
+      this.calendarInstance.setTheme(newTheme);
+    },
+    onClickTodayButton() {
+      this.calendarInstance.today();
+      this.setDateRangeText();
+    },
+    onClickMoveButton(offset) {
+      this.calendarInstance.move(offset);
+      this.setDateRangeText();
+    },
+    setDateRangeText() {
+      const date = this.calendarInstance.getDate();
+      const start = this.calendarInstance.getDateRangeStart();
+      const end = this.calendarInstance.getDateRangeEnd();
+
+      const startYear = start.getFullYear();
+      const endYear = end.getFullYear();
+
+      switch (this.selectedView) {
+        case 'month':
+          this.dateRangeText = `${date.getFullYear()}年${date.getMonth() + 1}月`;
+
+          return;
+        case 'day':
+          this.dateRangeText = `${date.getFullYear()}年${date.getMonth() + 1}月${date.getDate()}日`;
+          return;
+        default:
+          this.dateRangeText = `${startYear}年${start.getMonth() + 1}月${start.getDate()}日 - ${
+            startYear !== endYear ? `${endYear}年` : ''
+          }${end.getMonth() + 1}月${end.getDate()}日`;
+      }
+    },
+  },
+};
+</script>
+<style scoped>
+</style>

+ 23 - 0
src/components/calendar/calendar.css

@@ -0,0 +1,23 @@
+.buttons {
+  display: inline-block;
+  margin-left: 10px;
+}
+
+.buttons > button {
+  margin-left: 5px;
+  margin-right: 5px;
+}
+
+.buttons > button:last-child {
+  margin-right: 0;
+}
+
+.date-range {
+  margin-left: 10px;
+  margin-right: 10px;
+}
+
+.toastui-calendar-grid-cell-date {
+  line-height: 1.7;
+  text-align: center;
+}

+ 99 - 0
src/components/calendar/mock-data.js

@@ -0,0 +1,99 @@
+import { TZDate } from './tuical/toastui-calendar';
+
+import { addDate, addHours, subtractDate } from './utils';
+
+const today = new TZDate();
+
+/*
+* @property {string} [id] - Event id.
+* @property {string} [calendarId] - Calendar id.
+* @property {string} [title] - Event title.
+* @property {string} [body] - Body content of the event.
+* @property {string} [isAllday] - Whether the event is all day or not.
+* @property {string|number|Date|TZDate} [start] - Start time of the event.
+* @property {string|number|Date|TZDate} [end] - End time of the event.
+* @property {number} [goingDuration] - Travel time which is taken to go in minutes.
+* @property {number} [comingDuration] - Travel time which is taken to come back in minutes.
+* @property {string} [location] - Location of the event.
+* @property {Array.<string>} [attendees] - Attendees of the event.
+* @property {string} [category] - Category of the event. Available categories are 'milestone', 'task', 'time' and 'allday'.
+* @property {string} [dueDateClass] - Classification of work events. (before work, before lunch, before work)
+* @property {string} [recurrenceRule] - Recurrence rule of the event.
+* @property {string} [state] - State of the event. Available states are 'Busy', 'Free'.
+* @property {boolean} [isVisible] - Whether the event is visible or not.
+* @property {boolean} [isPending] - Whether the event is pending or not.
+* @property {boolean} [isFocused] - Whether the event is focused or not.
+* @property {boolean} [isReadOnly] - Whether the event is read only or not.
+* @property {boolean} [isPrivate] - Whether the event is private or not.
+*/
+
+var events = [
+  {
+    id: '1',
+    calendarId: '0',
+    title: 'TOAST UI Calendar Study',
+    category: 'time',
+    start: today,
+    end: addHours(today, 3),
+  },
+  {
+    id: '2',
+    calendarId: '0',
+    title: 'Practice',
+    category: 'milestone',
+    start: addDate(today, 1),
+    end: addDate(today, 1),
+    isReadOnly: true,
+  },
+  {
+    id: '3',
+    calendarId: '0',
+    title: 'FE Workshop',
+    category: 'allday',
+    start: subtractDate(today, 2),
+    end: subtractDate(today, 1),
+    isReadOnly: true,
+  },
+  {
+    id: '4',
+    calendarId: '0',
+    title: 'Report',
+    category: 'time',
+    start: today,
+    end: addHours(today, 1),
+  },
+];
+
+var calendars = [
+  {
+    id: '0',
+    name: 'Private',
+    backgroundColor: '#9e5fff',
+    borderColor: '#9e5fff',
+    dragBackgroundColor: '#9e5fff',
+  },
+  {
+    id: '1',
+    name: 'Company',
+    backgroundColor: '#00a9ff',
+    borderColor: '#00a9ff',
+    dragBackgroundColor: '#00a9ff',
+  },
+];
+
+let param = encodeURIComponent(JSON.stringify({start:'', end:''}));
+m3.callFS("/matrix/calendar/load.js", param).then( (res)=>{
+  //console.log(res)
+  if(res.status == "ok" && res.message && res.message.data && res.message.data.length > 0) {
+    for(var i in res.message.data) {
+      var dat = res.message.data[i];
+      dat.id = events.length;
+      // console.log(dat);
+      events.push(dat);
+    }
+  }
+}).catch( (err)=>{
+  console.error(err);
+} );
+
+export var events = events, calendars = calendars;

+ 67 - 0
src/components/calendar/theme.js

@@ -0,0 +1,67 @@
+export const theme = {
+  common: {
+    border: '1px solid #ddd',
+    backgroundColor: 'white',
+    holiday: { color: '#f54f3d' },
+    saturday: { color: '#135de6' },
+    dayName: { color: '#333' },
+    today: { color: '#009688' },
+    gridSelection: {
+      backgroundColor: 'rgba(19, 93, 230, 0.1)',
+      border: '1px solid #135de6',
+    },
+  },
+  month: {
+    dayName: {
+      borderLeft: 'none',
+      backgroundColor: 'inherit',
+    },
+    holidayExceptThisMonth: { color: '#f3acac' },
+    dayExceptThisMonth: { color: '#bbb' },
+    weekend: { backgroundColor: '#fafafa' },
+    moreView: { 
+      boxShadow: 'none',
+    },
+    moreViewTitle: {
+      backgroundColor: '#f4f4f4',
+    },
+  },
+  week: {
+    dayName: {
+      borderTop: '1px solid #ddd',
+      borderBottom: '1px solid #ddd',
+      borderLeft: '1px solid #ddd',
+      backgroundColor: 'inherit',
+    },
+    today: {
+      color: '#009688',
+      backgroundColor: 'inherit',
+    },
+    pastDay: { color: '#999' },
+    panelResizer: { border: '1px solid #ddd' },
+    dayGrid: { borderRight: '1px solid #ddd' },
+    dayGridLeft: {
+      width: '100px',
+      backgroundColor: '',
+      borderRight: '1px solid #ddd',
+    },
+    weekend: { backgroundColor: 'inherit' },
+    timeGridLeft: {
+      width: '100px',
+      backgroundColor: '#fafafa',
+      borderRight: '1px solid #ddd',
+    },
+    timeGridLeftAdditionalTimezone: { backgroundColor: '#fdfdfd' },
+    timeGridHourLine: { borderBottom: '1px solid #eee' },
+    timeGridHalfHourLine: { borderBottom: '1px dotted #f9f9f9' },
+    timeGrid: { borderRight: '1px solid #ddd' },
+    nowIndicatorLabel: { color: '#135de6' },
+    nowIndicatorPast: { border: '1px solid rgba(19, 93, 230, 0.3)' },
+    nowIndicatorBullet: { backgroundColor: '#135de6' },
+    nowIndicatorToday: { border: '1px solid #135de6' },
+    nowIndicatorFuture: { border: '1px solid #135de6' },
+    pastTime: { color: '#999' },
+    futureTime: { color: '#333' },
+    gridSelection: { color: '#135de6' },
+  },
+};

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 1123 - 0
src/components/calendar/tuical/toastui-calendar.css


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 27296 - 0
src/components/calendar/tuical/toastui-calendar.ie11.js


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 9 - 0
src/components/calendar/tuical/toastui-calendar.ie11.min.js


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 19597 - 0
src/components/calendar/tuical/toastui-calendar.js


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 6 - 0
src/components/calendar/tuical/toastui-calendar.min.css


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 9 - 0
src/components/calendar/tuical/toastui-calendar.min.js


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 10598 - 0
src/components/calendar/tuical/toastui-calendar.mjs


+ 26 - 0
src/components/calendar/utils.js

@@ -0,0 +1,26 @@
+import { TZDate } from './tuical/toastui-calendar';
+
+export function clone(date) {
+  return new TZDate(date);
+}
+
+export function addHours(d, step) {
+  const date = clone(d);
+  date.setHours(d.getHours() + step);
+
+  return date;
+}
+
+export function addDate(d, step) {
+  const date = clone(d);
+  date.setDate(d.getDate() + step);
+
+  return date;
+}
+
+export function subtractDate(d, steps) {
+  const date = clone(d);
+  date.setDate(d.getDate() - steps);
+
+  return date;
+}

+ 102 - 0
src/components/layout/Footer.vue

@@ -0,0 +1,102 @@
+<template>
+    <div class="footer">
+        <span>
+            版本:{{app.version}}
+        </span>
+        <el-divider direction="vertical"></el-divider>
+        <el-dropdown @command="onApiCommand" style="cursor:pointer;">
+            <span class="el-dropdown-link">
+                <i class="el-icon-tickets el-icon--right"></i> API
+            </span>
+            <el-dropdown-menu slot="dropdown">
+                <el-dropdown-item :command="item.url" v-for="item in api.list" :key="item.name">{{item.name}}</el-dropdown-item>
+            </el-dropdown-menu>
+        </el-dropdown>
+        <!-- <el-divider direction="vertical"></el-divider>
+        <el-dropdown @command="onLangCommand" style="cursor:pointer;">
+            <span class="el-dropdown-link">
+                <i class="el-icon-s-home el-icon--right"></i> {{lang.name}}
+            </span>
+            <el-dropdown-menu slot="dropdown">
+                <el-dropdown-item :command="item.value" v-for="item in lang" :key="item.name">{{item.name}}</el-dropdown-item>
+            </el-dropdown-menu>
+        </el-dropdown> -->
+        <el-divider direction="vertical"></el-divider>
+        <el-dropdown style="cursor:pointer;">
+            <span class="el-dropdown-link">
+                <i class="el-icon-coin el-icon--right"></i> {{company.name}}
+            </span>
+            <el-dropdown-menu slot="dropdown">
+                <el-dropdown-item>名称:{{company.name}}</el-dropdown-item>
+                <el-dropdown-item>应用:{{company.ospace}}</el-dropdown-item>
+            </el-dropdown-menu>
+        </el-dropdown>
+        <el-divider direction="vertical"></el-divider>
+        <el-link :href="company.web" target="_blank" :underline="false">
+            <i class="el-icon-user el-icon--right"></i> {{company.fullname}}
+        </el-link>
+    </div>
+</template>
+
+<script>
+    import _ from 'lodash';
+    export default{
+        name: "Footer",
+        props: {
+            auth: Object
+        },
+        data(){
+            return {
+                app: {
+                    year: "2021",
+                    version: "0.9.0"
+                },
+                api: {
+                    list: []
+                },
+                lang:{
+                    list: []
+                }
+            }
+        },
+        computed:{
+            company(){
+                return this.auth.Company;
+            }
+        },
+        created(){
+            this.m3.callFS('/matrix/footer/api_contextmenu.js', null).then((rtn)=>{
+                this.api.list = _.map(rtn.message,(v,k)=>{
+                    return {name: v.name, url: k, icon: v.icon};
+                });
+            }).catch((err)=>{
+                console.error(err)
+            });
+        },
+        methods:{
+            onLangCommand(cmd) {
+                window.location.href=`${window.location.origin}?lang=${cmd}`;
+            },
+            onApiCommand(cmd){
+                window.open(`${window.location.origin}${cmd}`,"_blank");
+            }
+        }
+    }
+</script>
+
+<style scoped>
+    .footer{
+        color: #888888;
+        height: 30px;
+        line-height:30px;
+        position: fixed;
+        bottom: 0px;
+        line-height: 30px;
+        right: 50px;
+        width: 100%;
+        text-align: right;
+    }
+    .footer .el-link{
+        color: #888888;
+    }
+</style>

+ 187 - 0
src/components/layout/Header.vue

@@ -0,0 +1,187 @@
+<template>
+    <el-row type="flex" :gutter="0" v-if="auth">
+        <el-col :span="19">
+            <el-link href="/" :underline="false">
+                <el-image :src="auth.Company.logo" fit="contain"></el-image>
+            </el-link>
+            <span style="font-size:16px;vertical-align: top;">{{auth.Company.title}}</span>
+        </el-col>
+        <el-col :span="3" style="text-align: right;">
+            <MessageView></MessageView>
+        </el-col>
+        <el-col :span="1" style="text-align: right;">
+            <div>
+                <el-button type="text" @click.stop="onToggleTheme($event)" class="toggle-theme" style="padding: 5px;border-radius: 10px;"></el-button>
+            </div>
+        </el-col>
+        <el-col :span="2">
+            <el-menu :default-active="activeIndex" 
+                    class="topbar-el-menu" 
+                    mode="horizontal" 
+                    menu-trigger="hover"
+                    @select="onSelect">
+                <el-submenu index="1">
+                    <template slot="title">
+                        <svg-icon icon-class="user"/> {{ auth.username }}
+                    </template>
+                    <el-menu-item index="/matrix/user">
+                        <template slot="title">
+                            <svg-icon icon-class="user2"/> 
+                            <span slot="title">用户</span>
+                        </template>
+                    </el-menu-item>
+                    <el-menu-item index="/matrix/system" divided v-if="auth.isadmin">
+                        <template slot="title">
+                            <svg-icon icon-class="system"/>
+                            <span slot="title">系统管理</span>
+                        </template>
+                    </el-menu-item>
+                    <el-menu-item index="/matrix/files" v-if="auth.isadmin">
+                        <template slot="title">
+                            <svg-icon icon-class="folder"/>
+                            <span slot="title">我的文件</span>
+                        </template>
+                    </el-menu-item>
+                    <el-menu-item index="home" v-if="auth.isadmin">
+                        <template slot="title">
+                            <svg-icon icon-class="home"/>
+                            <span slot="title">默认首页</span>
+                        </template>
+                    </el-menu-item>
+                    <el-menu-item index="signout" divided>
+                        <template slot="title">
+                            <svg-icon icon-class="logout"/> 
+                            <span slot="title">注销</span>
+                        </template>
+                    </el-menu-item>
+                </el-submenu>
+            </el-menu>
+        </el-col>
+    </el-row>    
+</template>
+
+<script>
+import _ from 'lodash';
+import Cookies from 'js-cookie';
+import MessageView from '../../components/message/MessageView';
+
+export default{
+    name: "Header",
+    props: {
+        auth: Object
+    },
+    data(){
+        return {
+            activeIndex: '1'
+        }
+    },
+    components: {
+        MessageView
+    },
+    created(){
+        this.initTheme();
+        this.m3.html.setTitle(this.auth);
+    },
+    mounted(){
+    },
+    methods: {
+        onSelect(key) {
+            if(_.startsWith(key,'/matrix/')){
+                window.open(key, '_blank');
+            } else {
+                if(key === 'home'){
+                    this.m3.html.setAppAsHome(this,{url:'/home'});
+                } else if(key==='signout'){
+                    window.open(`/user/logout/${this.auth.Company.name}`,'_parent');
+                } 
+            }
+        },
+        initTheme(){
+            window.m3.theme.initTheme();
+            let body = document.body;
+            let value = Cookies.get('m3-theme')?Cookies.get('m3-theme'):'dark';
+            body.classList.add(value);
+        },
+        onToggleTheme(event){
+            let val=Cookies.get("m3-theme")=='dark'?'light':'dark';
+            window.m3.theme.setTheme(val);
+            let body = document.body;
+            body.classList.remove(body.classList.item(0))
+            body.classList.add(val);
+        }
+    }
+}
+</script>
+
+<style>
+    .dark .toggle-theme, .dark .toggle-theme.el-button--text:focus, .dark .toggle-theme.el-button--text:active, .dark .toggle-theme.el-button--text:hover {
+        background: #1890ff;
+    }
+    .light .toggle-theme, .light .toggle-theme.el-button--text:focus, .light .toggle-theme.el-button--text:active, .light .toggle-theme.el-button--text:hover {
+        background: #252D47;
+    }
+    .dark .m3 > .header{
+        height: 50px!important;
+        line-height: 50px;
+        /* background: rgb(37, 45, 71); */
+        background: #252D47;
+        color: #ffffff;
+        padding: 0px 0px 0px 10px;
+        position: fixed;
+        width: 100%;
+        z-index: 1000;
+    }
+    .light .m3 > .header{
+        height: 50px!important;
+        line-height: 50px;
+        /* background: rgb(37, 45, 71); */
+        background: #1890ff;
+        color: #ffffff;
+        padding: 0px 0px 0px 10px;
+    }
+    .m3 > .header .el-image > .el-image__inner{
+        max-width: 120px;
+        min-width: 32px;
+        width: 64px;
+        height: 32px;
+        padding: 7px 0px;
+    }
+    .m3 > .el-main{
+        padding: 0px;
+    }
+    .el-link.el-link--default {
+        color: #ffffff;
+    }
+    .topbar-el-menu .el-submenu__icon-arrow.el-icon-arrow-down{
+            color: #ffffff;
+        }
+        .el-menu-item:hover{
+            background-color: #409dfe!important;
+        }
+    .dark .m3 .topbar-el-menu.el-menu.el-menu--horizontal {
+            border-bottom: unset;
+            background: #242c46;
+        }
+    .light .m3 .topbar-el-menu.el-menu.el-menu--horizontal {
+            border-bottom: unset;
+            background: #1890ff;
+        }
+        .topbar-el-menu.el-menu.el-menu--horizontal >.el-menu-item {
+            height: 30px;
+            line-height: 30px;
+        }
+        .topbar-el-menu.el-menu.el-menu--horizontal>.el-submenu {
+            float: right;
+        }
+        .topbar-el-menu.el-menu.el-menu--horizontal >.el-submenu .el-submenu__title {
+            height: 50px;
+            line-height: 50px;
+            border-bottom: unset;
+            color: #ffffff;
+        }
+    .el-menu--horizontal>.el-menu-item:not(.is-disabled):focus, 
+    .el-menu--horizontal>.el-menu-item:not(.is-disabled):hover, 
+    .el-menu--horizontal>.el-submenu .el-submenu__title:hover {
+        background-color: #409dfe!important;
+    }
+</style>

+ 255 - 0
src/components/layout/SideBar.vue

@@ -0,0 +1,255 @@
+<template>
+    <el-menu :default-active="defaultActive"
+            mode="vertical"
+            @select="onSelect"
+            @open="onOpen" 
+            @close="onClose" 
+            :collapse="isCollapse"
+            :collapse-transition="false"
+            class="el-menu-vertical-sidebar"
+            text-color="#fff"
+            active-text-color="#ffd04b"
+            v-if="auth && model">
+
+        <!-- <el-menu-item index="toggle" style="display:">
+            <i :class="isCollapse?'el-icon-s-unfold':'el-icon-s-fold'" style="width:16px;color:#fff;"></i>
+        </el-menu-item>
+
+        <el-menu-item index="apps" v-if="auth.Company.name == 'wecise'">
+            <img :src="preFixIcon+'app.png'+postFixIcon" style="width:16px;"></img> 
+            <span style="padding-left:5px;" slot="title">应用</span>
+        </el-menu-item> -->
+        
+        <el-menu-item index="/">
+            <img :src="preFixIcon+'home.png'+postFixIcon" style="width:16px;"/>
+            <span style="padding-left:5px;" slot="title">首页</span>
+        </el-menu-item>
+        
+        <!-- 有模板情况-->
+        <el-submenu :index="item.name" v-for="item in model.template" :key="item.name" v-show="sideBarStatus === 0">
+            <template slot="title">
+                <img :src="item.icon | pickIcon" style="width:16px;"/>
+                <span style="padding-left:5px;font-size:12px;">{{ item.title }}【{{item.groups.length}}】</span>
+            </template>
+            
+            <el-menu-item-group>
+                <span slot="title" style="font-size:12px;">{{ item.title }}【{{item.groups.length}}】</span>
+                <el-menu-item :class="subItem.status" :index="subItem.url" v-for="subItem in item.groups"  :key="subItem.name">
+                    <img :src="subItem.icon | pickIcon" style="width:16px;"/>
+                    <span slot="title">
+                        <span style="padding-left:5px;" v-if="global.register.lanuage == 'zh_CN'">{{subItem.cnname}}</span>
+                        <span style="padding-left:5px;" v-else>{{subItem.enname}}</span>
+                        <el-tooltip content="在新窗口中打开" placement="right-start">
+                            <el-button type="text" icon="el-icon-position" @click.stop.prevent="onClick(subItem.url)" style="float:right;transform:scale(0.6);color:#ffffff;"></el-button>
+                        </el-tooltip>
+                    </span>
+                </el-menu-item>
+            </el-menu-item-group>
+        </el-submenu>
+
+        <!-- 没有模板情况,且菜单项数量超过阈值-->
+        <el-submenu index="appList" v-show="sideBarStatus === 1">
+            <template slot="title">
+                <i class="fas fa-cubes" style="color:#ffffff;font-size:18px;"></i>
+                <span>应用</span>
+            </template>
+
+            <el-menu-item-group>
+                <span slot="title">应用</span>
+                <el-menu-item :class="item.status" :index="item.url" v-for="item in model.list" :key="item.name">
+                    <img :src="item.icon | pickIcon" style="width:16px;"/>
+                    <span slot="title">
+                        <span style="padding-left:5px;" v-if="global.register.lanuage == 'zh_CN'">{{item.cnname}}</span>
+                        <span style="padding-left:5px;" v-else>{{item.enname}}</span>
+                        <el-tooltip content="在新窗口中打开" placement="right-start">
+                            <el-button type="text" icon="el-icon-position" @click.stop.prevent="onClick(item.url)" style="float:right;transform:scale(0.6);color:#ffffff;"></el-button>
+                        </el-tooltip>
+                    </span>
+                </el-menu-item>
+            </el-menu-item-group>
+        </el-submenu>
+
+        <!-- 没有模板情况,且菜单项数量没超过阈值-->
+        <el-menu-item :class="item.status" :index="item.url" v-for="item in model.list" :key="item.name" v-show="sideBarStatus === 2">
+            <img :src="item.icon | pickIcon" style="width:16px;"/>
+            <span slot="title">
+                <span style="padding-left:5px;" v-if="global.register.lanuage == 'zh_CN'">{{item.cnname}}</span>
+                <span style="padding-left:5px;" v-else>{{item.enname}}</span>
+                <el-tooltip content="在新窗口中打开" placement="right-start">
+                    <el-button type="text" icon="el-icon-position" @click.stop.prevent="onClick(item.url)" style="float:right;transform:scale(0.6);color:#ffffff;"></el-button>
+                </el-tooltip>
+            </span>
+        </el-menu-item>
+
+        <!-- 没有分组的应用-->
+        <el-submenu :class="item.status" :index="item.url" v-for="item in model.appListUnGrouped" :key="item.name">
+            <template slot="title">
+                <img :src="item.icon | pickIcon" style="width:16px;"/>
+                <span style="padding-left:5px;" v-if="global.register.lanuage == 'zh_CN'">{{item.cnname}}</span>
+                <span style="padding-left:5px;" v-else>{{item.enname}}</span>
+            </template>
+            <el-menu-item :index="item.url">
+                <span style="padding-left:5px;" v-if="global.register.lanuage == 'zh_CN'">{{item.cnname}}</span>
+                <span style="padding-left:5px;" v-else>{{item.enname}}</span>
+                <el-tooltip content="在新窗口中打开" placement="right-start">
+                    <el-button type="text" icon="el-icon-position" @click.stop.prevent="onClick(item.url)" style="float:right;transform:scale(0.6);color:#ffffff;"></el-button>
+                </el-tooltip>
+            </el-menu-item>
+        </el-submenu>
+
+    </el-menu>
+</template>
+
+<script>
+import _ from 'lodash';
+
+export default{
+    name: "SideBar",
+    props: {
+        auth: Object,
+        global: Object
+    },
+    data(){
+        return {
+            model: null,
+            preFixIcon: `${window.assetsURLBase}/images/apps/png/`,
+            postFixIcon: '',
+            isCollapse: true,
+            defaultActive: '/matrix/home',
+            appConfig: [],
+            sideBarStatus: 2
+        }
+    },
+    created(){
+        this.init();
+    },
+    mounted(){
+        this.defaultActive = window.location.pathname;
+    },
+    filters:{
+        pickIcon(icon){
+            return `${window.assetsURLBase}/images/apps/png/${icon}`;
+        }
+    },
+    methods: {
+        init(){
+            this.m3.callFS("/matrix/m3appstore/user.js", this.auth.username).then( (val)=>{
+                let rtn = val.message;
+                
+                this.model = {
+                    list: _.map(rtn.appListSelected,(v)=>{
+                            /* let _page = _.last(mx.getPage().split("/"));
+
+                            if(_.endsWith(v.url,_page)){
+                                return _.merge(v, {status: "active"});
+                            } */
+
+                            return _.merge(v, {status: ""});
+                        }),
+                    template: rtn.template,
+                    appListUnGrouped: rtn.appListUnGrouped
+                };
+
+                this.setStatus();
+            } );
+            
+        },
+        setStatus(){
+            // 有模板情况
+            if(!_.isEmpty(this.model.template)){
+                this.sideBarStatus = 0;
+            } 
+            // 没有模板情况
+            else{
+                // 菜单项超过阈值
+                if(this.model.list.length > this.global.register.sidebar.menuCollapse){
+                    this.sideBarStatus = 1;
+                }
+                // 菜单项没有超过阈值
+                else {
+                    this.sideBarStatus = 2;
+                }
+            }
+        },
+        initWnd(){
+
+            /* setTimeout(() => {
+                this.wnd = maxWindow.winApps(`应用市场`, `<div id="nav-menu-level1" style="width:100%;height:100%;"></div>`, null, 'apps-container');
+                inst.app = new Vue(inst.appBox()).$mount("#nav-menu-level1");
+            }, 50); */
+
+        },
+        refresh(){
+            this.init();
+        },
+        onToggle(){
+            this.isCollapse = !this.isCollapse;
+        },
+        onSelect(index,indexPath){
+            if(index == 'toggle'){
+                this.onToggle();
+            } else if(index == 'apps'){
+                this.initWnd();
+            } else {
+                window.open(index,'_parent');
+            }
+        },
+        onClick(url){
+            window.open(url,'_blank')
+        },
+        onOpen(key, keyPath) {
+            console.log(key, keyPath);
+        },
+        onClose(key, keyPath) {
+            console.log(key, keyPath);
+        }
+    }
+}
+</script>
+
+<style scoped>
+    .dark .m3 > .sidebar{
+        width: 60px;
+    }
+
+    .el-menu-vertical-sidebar:not(.el-menu--collapse){
+        width: 200px;
+        height: 100vh;
+    }
+
+    .el-menu--collapse {
+        width: 64px;
+        height: calc(100vh - 50px);
+    }
+
+    .el-menu {
+        border-right: solid 1px #e6e6e6;
+        list-style: none;
+        flex: 0 0 auto;
+        margin: 0;
+        padding-left: 0;
+        background-color: #252d47;
+    }
+
+    .el-menu-item i {
+        color: #ffffff;
+    }
+    .el-menu--collapse>.el-menu-item.is-active i {
+        color: #ffffff;
+    }
+</style>
+
+<style>
+    /* .el-menu:not(.el-menu--popup-bottom-start), 
+    .el-menu--horizontal>.el-menu-item:not(.is-disabled):focus, 
+    .el-menu--horizontal>.el-menu-item:not(.is-disabled):hover, 
+    .el-menu--horizontal>.el-submenu .el-submenu__title:hover {
+        background-color: #252d47!important;
+    }
+    .el-submenu__title:hover{
+        background-color: #409dfe!important;
+    }
+    .el-submenu:hover {
+        background-color: #409dfe!important;
+    } */
+</style>

+ 106 - 0
src/components/message/MessageView.vue

@@ -0,0 +1,106 @@
+<template>
+  <div>
+    <el-badge :is-dot="true" style="line-height: 25px;margin-top: 5px;" @click.native="dialog.message.show = true;">
+      <el-button style="padding: 5px;" size="small" type="primary">
+        <svg-icon icon-class="message" style="width: 1.2em;height: 1.2em;"/>
+      </el-button>
+    </el-badge>
+
+    <el-dialog :visible.sync="dialog.message.show"
+      append-to-body 
+      custom-class="message-dialog"
+      @show="initData">
+      
+      <div slot="title">
+        <h3>消息</h3>
+        <!-- <el-button size="mini" type="primary" icon="el-icon-delete" @click="onClearAll">清理消息</el-button> -->
+      </div>
+      <el-container style="height: calc(100vh - 440px);">
+        <el-main style="overflow:hidden;">
+            <el-tabs value="audit" type="border-card">
+              <el-tab-pane label="消息" name="message"></el-tab-pane>
+              <el-tab-pane label="系统日志" name="audit">
+                <ul class="infinite-list" v-infinite-scroll="initData" style="overflow:auto;height:calc(100vh - 530px);">
+                  <li v-for="row in dt.rows" class="infinite-list-item" :key="row.id">
+                    {{ row.user }} <el-divider direction="vertical"></el-divider> 
+                    {{ row.module }} <el-divider direction="vertical"></el-divider>
+                    <el-popover
+                      placement="top-start"
+                      width="200"
+                      trigger="hover"
+                      :content="row.operation">
+                      <el-button slot="reference">{{ row.operation | formatStr }}</el-button>
+                    </el-popover>
+                    <el-divider direction="vertical"></el-divider>
+                    {{ row.source }} <el-divider direction="vertical"></el-divider>
+                    {{ row.vtime | formatDate}}</li>
+                </ul>
+              </el-tab-pane>
+            </el-tabs>
+        </el-main>
+      </el-container>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import _ from 'lodash';
+
+export default {
+  name: 'MessageView',
+  data() {
+    return {
+      dt: {
+          rows: []
+      },
+      dialog:{
+        message:{
+          show: false
+        }
+      }
+    }
+  },
+  filters:{
+    formatStr(val){
+      return _.truncate(val);
+    },
+    formatDate(val){
+      return new Date(val).toLocaleString();
+    }
+  },
+  methods: {
+    initData() {
+      this.m3.callFS("/matrix/m3event/message/getMessage.js").then(rtn=>{
+        this.dt.rows = rtn.message;
+      })
+    }
+  }
+}
+</script>
+
+<style scoped>
+  .infinite-list {
+      height: 100%;
+      padding: 0;
+      margin: 0;
+      list-style: none;
+  }
+
+  .infinite-list .infinite-list-item {
+      display: flex;
+      align-items: center;
+      justify-content: left;
+      height: 40px;
+      background: #f2f2f2;
+      margin: 10px;
+      color: #101323;
+      padding:0 20px;
+  }
+</style>
+
+<style>
+  .message-dialog.el-dialog {
+      width: 60vw!important;
+      height: 70vh;
+  }
+</style>

+ 68 - 0
src/main.js

@@ -0,0 +1,68 @@
+// 静态加载依赖组件
+import Vue from 'vue'
+import ElementUI from 'element-ui'
+import 'element-ui/lib/theme-chalk/index.css'
+import zhLocale from 'element-ui/lib/locale/lang/zh-CN'
+
+// 开始动态加载依赖组件
+window.state && window.state("正在加载依赖组件...")
+
+// 动态加载模块依赖关系
+// auth,global,Vue... 为 m3 内部加载的模块, 这里的定义只为标明依赖关系时引用, 初始化时会自动替换为m3内部定义
+let mods = {auth:{}, global:{}, Vue:{}}
+// 定义应用所需动态加载模块
+mods.App = {
+    f: () => import(`@/App.vue`),  // 动态加载 App.vue
+    deps: [mods.global, mods.auth, mods.Vue] // App.vue 运行时依赖 global 和 auth
+}
+
+// m3小应用开发框架配置信息
+let m3config = {
+    global: window,   //全局变量,默认为window
+    rootDivID: "app", //配合vue
+    lang: "zh-CN", 
+    theme: "", // 默认使用cookie中保存的信息或使用内置缺省值
+    displayLoadingState: true,
+    mods,
+}
+
+// 加载m3js
+import("@wecise/m3js").then((m3)=>{
+    m3.go(m3config)
+    /*******************************************************************************************
+     **** m3.go 所做的操作如下。如需定制化处理,可以使用下面的代码代替 m3.go(m3config) ****
+     *******************************************************************************************
+    window.state && window.state("正在初始化小应用配置...")
+    // m3js加载完成,根据配置信息动态有序异步加载依赖组件,完成M3小应用初始化
+    m3.init(m3config).then(()=>{
+        window.state && window.state("正在渲染页面...")
+        // 设置基本样式
+        m3.merge(Vue.prototype.$ELEMENT, {
+            size: m3.cookie.get('size') || 'mini',
+        })        // m3.render完成的工作是渲染Vue页面,也可以写成
+        // new Vue({
+        //     render: h => h(window.App),
+        //     mounted: function(){
+        //         window.state && window.state("页面渲染完成...")
+        //         m3.completed() // 加载数据
+        //     },
+        // }).$mount('#app')
+        m3.render().then(()=>{
+            window.state && window.state("正在加载数据...")
+            // 此时Vue渲染已经完成,但是页面还不能正常显示,还需要加载页面相关数据,并驱动Vue相关组件更新显示状态
+            m3.completed().then(()=>{
+                // 此时页面才能正常显示
+                window.state && window.state("页面输出完成.");
+            }).catch((e)=>{
+                console.error(e)
+            })
+        }).catch((e)=>{
+            console.error(e)
+        })
+    }).catch((e)=>{
+        console.error(e)
+    })
+    /**** m.default.go ****/
+}).catch((e)=>{
+    console.error(e)
+});