Browse Source

Table is supported when creating a new item
新建项目的时候支持表格类型

star7th 5 years ago
parent
commit
71d3fbc60f

+ 16 - 45
server/Application/Api/Controller/ItemController.class.php

@@ -34,14 +34,8 @@ class ItemController extends BaseController {
             $this->sendError(10101,'项目不存在或者已删除');
             return false;
         }
-        if ($item['item_type'] == 1 ) {
-            $this->_show_regular_item($item);
-        }
-        elseif ($item['item_type'] == 2 ) {
-            $this->_show_single_page_item($item);
-        }else{
-           $this->_show_regular_item($item); 
-        }
+        //从2020.7.5开始,常规项目和单页项目合并在一起返回
+        $this->_show_regular_item($item); 
     }
 
     //展示常规项目
@@ -49,6 +43,7 @@ class ItemController extends BaseController {
         $item_id = $item['item_id'];
 
         $default_page_id = I("default_page_id/d");
+        $current_page_id = I("page_id/d");
         $keyword = I("keyword");
         $default_cat_id2 = $default_cat_id3 = 0 ;
 
@@ -129,44 +124,7 @@ class ItemController extends BaseController {
             "is_login"=>$is_login,
             "ItemPermn"=>$ItemPermn ,
             "ItemCreator"=>$ItemCreator ,
-
-            );
-        $this->sendResult($return);
-    }
-
-    //展示单页项目
-    private function _show_single_page_item($item){
-        $item_id = $item['item_id'];
-
-        $current_page_id = I("page_id/d");
-
-        $login_user = session("login_user");
-        $uid = $login_user['uid'] ? $login_user['uid'] : 0 ;
-        $is_login =   $uid > 0 ? true :false;
-        //获取页面
-        $page = D("Page")->where(" item_id = '$item_id' ")->find();
-
-        $domain = $item['item_domain'] ? $item['item_domain'] : $item['item_id'];
-        $share_url = get_domain().__APP__.'/'.$domain;
-
-        $ItemPermn = $this->checkItemPermn($uid , $item_id) ;
-
-        $ItemCreator = $this->checkItemCreator($uid , $item_id);
-
-        $menu = array() ;
-        $menu['pages'] = $page ;
-        $return = array(
-            "item_id"=>$item_id ,
-            "item_domain"=>$item['item_domain'] ,
-            "is_archived"=>$item['is_archived'] ,
-            "item_name"=>$item['item_name'] ,
             "current_page_id"=>$current_page_id ,
-            "unread_count"=>$unread_count ,
-            "item_type"=>2 ,
-            "menu"=>$menu ,
-            "is_login"=>$is_login,
-            "ItemPermn"=>$ItemPermn ,
-            "ItemCreator"=>$ItemCreator ,
 
             );
         $this->sendResult($return);
@@ -594,6 +552,19 @@ class ItemController extends BaseController {
                     );
                 $page_id = D("Page")->add($insert);
             }
+            //如果是表格应用,则新建一个默认页
+            if ($item_type == 4 ) {
+                $insert = array(
+                    'author_uid' => $login_user['uid'] ,
+                    'author_username' => $login_user['username'],
+                    "page_title" => $item_name ,
+                    "item_id" => $item_id ,
+                    "cat_id" => 0 ,
+                    "page_content" => '' ,
+                    "addtime" =>time()
+                    );
+                $page_id = D("Page")->add($insert);
+            }
             $this->sendResult(array("item_id"=>$item_id));               
         }else{
             $this->sendError(10101);

+ 2 - 7
web_src/src/components/item/add/Copy.vue

@@ -1,7 +1,7 @@
 <template>
   <div class="hello">
     <p class="tips">{{$t('copy_item_tips1')}}</p>
-    <el-form status-icon label-width="100px" class="infoForm" v-model="infoForm">
+    <el-form status-icon label-width="10px" class="infoForm" v-model="infoForm">
       <el-form-item label class="text-left">
         <el-select
           style="width:100%;"
@@ -113,15 +113,10 @@ export default {
 }
 
 .infoForm {
-  width: 400px;
+  width: 380px;
   margin-top: 50px;
 }
 
-.goback-btn {
-  z-index: 999;
-  margin-left: 500px;
-}
-
 .tips {
   margin-left: 10px;
   color: #9ea1a6;

+ 5 - 9
web_src/src/components/item/add/Import.vue

@@ -59,18 +59,14 @@ export default {
 <style scoped>
 .hello {
   text-align: left;
-  margin-left: 50px;
-  margin-top: 50px;
-}
-
-.goback-btn {
-  z-index: 999;
-  margin-left: 500px;
+  margin-left: 10px;
+  margin-top: 30px;
 }
 
 .tips {
-  margin-left: 10px;
-  margin-bottom: 50px;
+  margin-left: 5px;
+  margin-bottom: 20px;
   color: #9ea1a6;
+  padding: 10px;
 }
 </style>

+ 2 - 8
web_src/src/components/item/add/Index.vue

@@ -10,14 +10,10 @@
           </el-button>
 
           <el-tabs value="first" type="card">
-            <el-tab-pane :label="$t('regular_item')" name="first">
+            <el-tab-pane :label="$t('new_item')" name="first">
               <Regular></Regular>
             </el-tab-pane>
 
-            <el-tab-pane :label="$t('single_item')" name="second">
-              <Single></Single>
-            </el-tab-pane>
-
             <el-tab-pane :label="$t('copy_item')" name="third">
               <Copy></Copy>
             </el-tab-pane>
@@ -40,7 +36,6 @@
 
 <script>
 import Regular from '@/components/item/add/Regular'
-import Single from '@/components/item/add/Single'
 import Copy from '@/components/item/add/Copy'
 import OpenApi from '@/components/item/add/OpenApi'
 import Import from '@/components/item/add/Import'
@@ -49,7 +44,6 @@ export default {
   name: 'Login',
   components: {
     Regular,
-    Single,
     Copy,
     OpenApi,
     Import
@@ -97,7 +91,7 @@ export default {
 
 .center-card {
   text-align: center;
-  width: 560px;
+  width: 463px;
   min-height: 600px;
   max-height: 800px;
 }

+ 2 - 7
web_src/src/components/item/add/OpenApi.vue

@@ -35,12 +35,7 @@ export default {
 <style scoped>
 .hello {
   text-align: left;
-  margin-left: 50px;
-  margin-top: 80px;
-}
-
-.goback-btn {
-  z-index: 999;
-  margin-left: 500px;
+  margin-left: 20px;
+  margin-top: 50px;
 }
 </style>

+ 23 - 10
web_src/src/components/item/add/Regular.vue

@@ -1,6 +1,23 @@
 <template>
   <div class="hello">
-    <el-form status-icon label-width="100px" class="infoForm" v-model="infoForm">
+    <el-form status-icon label-width="10px" class="infoForm" v-model="infoForm">
+      <el-form-item>
+        <el-radio-group v-model="infoForm.item_type">
+          <el-radio label="1">{{$t('regular_item')}}</el-radio>
+          <el-radio label="4">{{$t('table')}}</el-radio>
+          <el-radio label="2">
+            {{$t('single_item')}}
+            <el-tooltip
+              class="item"
+              effect="dark"
+              :content="$t('single_item_tips')"
+              placement="top"
+            >
+              <i class="el-icon-question"></i>
+            </el-tooltip>
+          </el-radio>
+        </el-radio-group>
+      </el-form-item>
       <el-form-item>
         <el-tooltip class="item" effect="dark" :content="$t('item_name')" placement="right">
           <el-input
@@ -54,7 +71,8 @@ export default {
         item_name: '',
         item_description: '',
         item_domain: '',
-        password: ''
+        password: '',
+        item_type: '1'
       },
       isOpenItem: true
     }
@@ -71,7 +89,7 @@ export default {
         this.infoForm.password = ''
       }
       var params = new URLSearchParams()
-      params.append('item_type', 1)
+      params.append('item_type', this.infoForm.item_type)
       params.append('item_name', this.infoForm.item_name)
       params.append('item_description', this.infoForm.item_description)
       params.append('item_domain', this.infoForm.item_domain)
@@ -98,12 +116,7 @@ export default {
 }
 
 .infoForm {
-  width: 400px;
-  margin-top: 60px;
-}
-
-.goback-btn {
-  z-index: 999;
-  margin-left: 500px;
+  width: 380px;
+  margin-top: 30px;
 }
 </style>

+ 0 - 115
web_src/src/components/item/add/Single.vue

@@ -1,115 +0,0 @@
-<template>
-  <div class="hello">
-    <p class="tips">{{$t('single_item_tips')}}</p>
-    <el-form status-icon label-width="100px" class="infoForm" v-model="infoForm">
-      <el-form-item>
-        <el-tooltip class="item" effect="dark" :content="$t('item_name')" placement="right">
-          <el-input
-            type="text"
-            auto-complete="off"
-            v-model="infoForm.item_name"
-            :placeholder="$t('item_name')"
-          ></el-input>
-        </el-tooltip>
-      </el-form-item>
-
-      <el-form-item>
-        <el-tooltip class="item" effect="dark" :content="$t('item_description')" placement="right">
-          <el-input
-            type="text"
-            auto-complete="off"
-            v-model="infoForm.item_description"
-            :placeholder="$t('item_description')"
-          ></el-input>
-        </el-tooltip>
-      </el-form-item>
-
-      <el-form-item label>
-        <el-radio v-model="isOpenItem" :label="true">{{$t('Open_item')}}</el-radio>
-        <el-radio v-model="isOpenItem" :label="false">{{$t('private_item')}}</el-radio>
-      </el-form-item>
-
-      <el-form-item v-show="!isOpenItem">
-        <el-input
-          type="password"
-          auto-complete="off"
-          v-model="infoForm.password"
-          :placeholder="$t('visit_password')"
-        ></el-input>
-      </el-form-item>
-
-      <el-form-item label>
-        <el-button type="primary" style="width:100%;" @click="FormSubmit">{{$t('submit')}}</el-button>
-      </el-form-item>
-    </el-form>
-  </div>
-</template>
-
-<script>
-export default {
-  name: 'Login',
-  components: {},
-  data() {
-    return {
-      infoForm: {
-        item_name: '',
-        item_description: '',
-        item_domain: '',
-        password: ''
-      },
-      isOpenItem: true
-    }
-  },
-  methods: {
-    FormSubmit() {
-      var that = this
-      var url = DocConfig.server + '/api/item/add'
-      if (!this.isOpenItem && !this.infoForm.password) {
-        that.$alert(that.$t('private_item_passwrod'))
-        return false
-      }
-      if (this.isOpenItem) {
-        this.infoForm.password = ''
-      }
-      var params = new URLSearchParams()
-      params.append('item_type', 2)
-      params.append('item_name', this.infoForm.item_name)
-      params.append('item_description', this.infoForm.item_description)
-      params.append('item_domain', this.infoForm.item_domain)
-      params.append('password', this.infoForm.password)
-
-      that.axios.post(url, params).then(function(response) {
-        if (response.data.error_code === 0) {
-          that.$router.push({ path: '/item/index' })
-        } else {
-          that.$alert(response.data.error_message)
-        }
-      })
-    }
-  },
-
-  mounted() {}
-}
-</script>
-
-<!-- Add "scoped" attribute to limit CSS to this component only -->
-<style scoped>
-.center-card a {
-  font-size: 12px;
-}
-
-.infoForm {
-  width: 400px;
-  margin-top: 20px;
-}
-
-.goback-btn {
-  z-index: 999;
-  margin-left: 500px;
-}
-
-.tips {
-  margin-left: 10px;
-  color: #9ea1a6;
-}
-</style>

+ 7 - 1
web_src/src/components/item/show/Index.vue

@@ -13,6 +13,9 @@
     <!-- 展示单页项目 -->
     <ShowSinglePageItem :item_info="item_info" v-if="item_info && item_info.item_type == 2 "></ShowSinglePageItem>
 
+    <!-- 展示表格项目 -->
+    <ShowTableItem :item_info="item_info" v-if="item_info && item_info.item_type == 4 "></ShowTableItem>
+
     <Footer></Footer>
   </div>
 </template>
@@ -22,6 +25,8 @@
 <script>
 import ShowRegularItem from '@/components/item/show/show_regular_item/Index'
 import ShowSinglePageItem from '@/components/item/show/show_single_page_item/Index'
+import ShowTableItem from '@/components/item/show/show_table_item/Index'
+
 export default {
   data() {
     return {
@@ -31,7 +36,8 @@ export default {
   },
   components: {
     ShowRegularItem,
-    ShowSinglePageItem
+    ShowSinglePageItem,
+    ShowTableItem
   },
   methods: {
     // 获取菜单

+ 1 - 1
web_src/src/components/item/show/show_single_page_item/Index.vue

@@ -224,7 +224,7 @@ export default {
   },
   mounted() {
     this.menu = this.item_info.menu
-    this.page_id = this.menu.pages.page_id
+    this.page_id = this.menu.pages[0].page_id
     this.get_page_content()
 
     // 根据屏幕宽度进行响应(应对移动设备的访问)

+ 421 - 0
web_src/src/components/item/show/show_table_item/Index.vue

@@ -0,0 +1,421 @@
+<template>
+  <div class="hello">
+    <link href="static/xspreadsheet/xspreadsheet.css" rel="stylesheet" />
+    <div id="header"></div>
+    <div class="edit-bar" v-if="item_info.ItemPermn">
+      <el-button type="primary" size="mini" @click="save">{{$t('save')}}</el-button>
+      <el-dropdown @command="dropdownCallback">
+        <el-button size="mini">
+          {{$t('more')}}
+          <i class="el-icon-arrow-down el-icon--right"></i>
+        </el-button>
+        <el-dropdown-menu slot="dropdown">
+          <el-dropdown-item :command="shareItem">{{$t('share')}}</el-dropdown-item>
+          <router-link :to="'/item/setting/'+item_info.item_id" v-if="item_info.ItemCreator">
+            <el-dropdown-item>{{$t('item_setting')}}</el-dropdown-item>
+          </router-link>
+          <el-dropdown-item :command="()=>{importDialogVisible = true}">{{$t('import_file')}}</el-dropdown-item>
+          <el-dropdown-item :command="exportFile">{{$t('export')}}</el-dropdown-item>
+          <el-dropdown-item :command="goback">{{$t('goback')}}</el-dropdown-item>
+        </el-dropdown-menu>
+      </el-dropdown>
+    </div>
+    <div class="edit-bar" v-if="!item_info.ItemPermn">
+      <el-button size="mini" @click="goback">{{$t('goback')}}</el-button>
+    </div>
+    <div id="table-item"></div>
+    <el-dialog
+      :title="$t('share')"
+      :visible.sync="dialogVisible"
+      width="600px"
+      :close-on-click-modal="false"
+      class="text-center"
+    >
+      <p>
+        {{$t('item_address')}} :
+        <code>{{share_item_link}}</code>
+      </p>
+      <p>
+        <a
+          href="javascript:;"
+          class="home-phone-butt"
+          v-clipboard:copyhttplist="copyText"
+          v-clipboard:success="onCopy"
+        >{{$t('copy_link')}}</a>
+      </p>
+      <p style="border-bottom: 1px solid #eee;">
+        <img id style="width:114px;height:114px;" :src="qr_item_link" />
+      </p>
+      <span slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="dialogVisible = false">{{$t('confirm')}}</el-button>
+      </span>
+    </el-dialog>
+
+    <el-dialog
+      :title="$t('import_excel')"
+      :visible.sync="importDialogVisible"
+      width="600px"
+      :close-on-click-modal="false"
+      class="text-center"
+    >
+      <p>
+        <input
+          type="file"
+          name="xlfile"
+          id="xlf"
+          @change="(e)=>{
+            improtFile(e.target.files)
+          }"
+        />
+      </p>
+      <span slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="importDialogVisible = false">{{$t('confirm')}}</el-button>
+      </span>
+    </el-dialog>
+  </div>
+</template>
+
+<style scoped>
+.edit-bar {
+  position: absolute;
+  right: 10px;
+  margin-top: 5px;
+}
+.edit-bar > button {
+  margin-right: 10px;
+}
+</style>
+
+<script>
+if (typeof window !== 'undefined') {
+  var $s = require('scriptjs')
+}
+export default {
+  props: {
+    item_info: ''
+  },
+  data() {
+    return {
+      menu: '',
+      content: '',
+      page_title: '',
+      page_id: '',
+      dialogVisible: false,
+      share_item_link: '',
+      qr_item_link: '',
+      copyText: '',
+      spreadsheetObj: {},
+      spreadsheetData: {},
+      isLock: 0,
+      isEditable: 0,
+      intervalId: 0,
+      importDialogVisible: false
+    }
+  },
+  components: {},
+  methods: {
+    getPageContent(page_id) {
+      var that = this
+      if (!page_id) {
+        page_id = that.page_id
+      }
+      this.request('/api/page/info', {
+        page_id: page_id
+      }).then(response => {
+        if (response.data.page_content) {
+          let objData
+          try {
+            // 先定义一个html反转义的函数
+            const unescapeHTML = str =>
+              str.replace(
+                /&amp;|&lt;|&gt;|&#39;|&quot;/g,
+                tag =>
+                  ({
+                    '&amp;': '&',
+                    '&lt;': '<',
+                    '&gt;': '>',
+                    '&#39;': "'",
+                    '&quot;': '"'
+                  }[tag] || tag)
+              )
+            objData = JSON.parse(
+              unescapeHTML(decodeURIComponent(response.data.page_content))
+            )
+          } catch (error) {
+            objData = {}
+          }
+          this.spreadsheetData = objData
+          // 初始化表格
+          this.initSheet()
+          if (this.item_info.ItemPermn) {
+            this.draft()
+          }
+        }
+      })
+    },
+    initSheet() {
+      if (!x_spreadsheet) return false
+      let mode = this.isEditable ? 'edit' : 'read'
+      document.getElementById('table-item').innerHTML = '' // 清空原来的东西
+      this.spreadsheetObj = null
+
+      // 初始化表格
+      this.spreadsheetObj = x_spreadsheet('#table-item', {
+        mode: mode, // edit | read
+        showToolbar: true,
+        row: {
+          len: 500,
+          height: 25
+        }
+      }).loadData(this.spreadsheetData) // load data
+    },
+
+    shareItem() {
+      let path = this.item_info.item_domain
+        ? this.item_info.item_domain
+        : this.item_info.item_id
+      this.share_item_link = this.getRootPath() + '/' + path
+      this.qr_item_link =
+        DocConfig.server +
+        '/api/common/qrcode&size=3&url=' +
+        encodeURIComponent(this.share_item_link)
+      this.dialogVisible = true
+      this.copyText =
+        this.item_info.item_name + '  -- ShowDoc \r\n' + this.share_item_link
+    },
+    onCopy() {
+      this.$message(this.$t('copy_success'))
+    },
+    save() {
+      this.request('/api/page/save', {
+        page_id: this.page_id,
+        page_title: this.item_info.item_name,
+        item_id: this.item_info.item_id,
+        is_urlencode: 1,
+        page_content: encodeURIComponent(
+          JSON.stringify(this.spreadsheetObj.getData())
+        )
+      }).then(data => {
+        // console.log(data)
+        this.$message({
+          showClose: true,
+          message: '保存成功',
+          type: 'success'
+        })
+        // 删除草稿
+        this.deleteDraft()
+      })
+    },
+    goback() {
+      this.$router.push({
+        path: '/item/index'
+      })
+      // 由于x_spreadsheet的固有缺陷,只能重新刷新销毁实例了
+      setTimeout(() => {
+        window.location.reload()
+      }, 200)
+    },
+    dropdownCallback(data) {
+      if (data) {
+        data()
+      }
+    },
+    // 草稿
+    draft() {
+      var that = this
+      var pkey = 'page_content_' + this.page_id
+      // 定时保存文本内容到localStorage
+      setInterval(() => {
+        var content = JSON.stringify(this.spreadsheetObj.getData())
+        localStorage.setItem(pkey, content)
+      }, 30 * 1000)
+
+      // 检测是否有定时保存的内容
+      var page_content = JSON.parse(localStorage.getItem(pkey))
+      if (
+        page_content &&
+        page_content.length > 0 &&
+        localStorage.getItem(pkey) !=
+          JSON.stringify(this.spreadsheetObj.getData())
+      ) {
+        localStorage.removeItem(pkey)
+        that
+          .$confirm(that.$t('draft_tips'), '', {
+            showClose: false
+          })
+          .then(() => {
+            this.spreadsheetData = page_content
+            // 初始化表格
+            this.initSheet()
+            localStorage.removeItem(pkey)
+          })
+          .catch(() => {
+            localStorage.removeItem(pkey)
+          })
+      }
+    },
+
+    // 遍历删除草稿
+    deleteDraft() {
+      for (var i = 0; i < localStorage.length; i++) {
+        var name = localStorage.key(i)
+        if (name.indexOf('page_content_') > -1) {
+          localStorage.removeItem(name)
+        }
+      }
+    },
+    // 锁定
+    setLock() {
+      if (this.page_id > 0) {
+        this.request('/api/page/setLock', {
+          page_id: this.page_id,
+          item_id: this.item_info.item_id
+        }).then(() => {
+          this.isLock = 1
+        })
+      }
+    },
+    // 解除锁定
+    unlock() {
+      if (!this.isLock) {
+        return // 本来处于未锁定中的话,不发起请求
+      }
+      this.request('/api/page/setLock', {
+        page_id: this.page_id,
+        item_id: this.item_info.item_id,
+        lock_to: 1000
+      }).then(() => {
+        this.isLock = 0
+      })
+    },
+    // 如果用户处于锁定状态的话,用心跳保持锁定
+    heartBeatLock() {
+      this.intervalId = setInterval(() => {
+        if (this.isLock) {
+          this.setLock()
+        }
+      }, 3 * 60 * 1000)
+    },
+    // 判断页面是否被锁定编辑
+    remoteIsLock() {
+      this.request('/api/page/isLock', {
+        page_id: this.page_id
+      }).then(res => {
+        // 判断已经锁定了不
+        if (res.data.lock > 0) {
+          if (res.data.is_cur_user > 0) {
+            this.isLock = 1
+            this.isEditable = 1
+            this.initSheet()
+            this.heartBeatLock()
+          } else {
+            this.$alert(this.$t('locking') + res.data.lock_username)
+            this.item_info.ItemPermn = false
+            clearInterval(this.intervalId)
+            this.deleteDraft()
+          }
+        } else {
+          this.setLock() // 如果没有被别人锁定,则进编辑页面后自己锁定。
+          this.isEditable = 1
+          this.initSheet()
+          this.heartBeatLock()
+        }
+      })
+    },
+    exportFile() {
+      // 先定义一个函数
+      const xtos = sdata => {
+        var out = XLSX.utils.book_new()
+        sdata.forEach(function(xws) {
+          var aoa = [[]]
+          var rowobj = xws.rows
+          for (var ri = 0; ri < rowobj.len; ++ri) {
+            var row = rowobj[ri]
+            if (!row) continue
+            aoa[ri] = []
+            Object.keys(row.cells).forEach(function(k) {
+              var idx = +k
+              if (isNaN(idx)) return
+              aoa[ri][idx] = row.cells[k].text
+            })
+          }
+          var ws = XLSX.utils.aoa_to_sheet(aoa)
+          XLSX.utils.book_append_sheet(out, ws, xws.name)
+        })
+        return out
+      }
+
+      /* build workbook from the grid data */
+      var new_wb = xtos(this.spreadsheetObj.getData())
+
+      /* generate download */
+      XLSX.writeFile(new_wb, 'showdoc.xlsx')
+    },
+    improtFile(files) {
+      const f = files[0]
+
+      const stox = wb => {
+        var out = []
+        wb.SheetNames.forEach(function(name) {
+          var o = { name: name, rows: {} }
+          var ws = wb.Sheets[name]
+          var aoa = XLSX.utils.sheet_to_json(ws, { raw: false, header: 1 })
+          aoa.forEach(function(r, i) {
+            var cells = {}
+            r.forEach(function(c, j) {
+              cells[j] = { text: c }
+            })
+            o.rows[i] = { cells: cells }
+          })
+          out.push(o)
+        })
+        return out
+      }
+      var reader = new FileReader()
+      reader.onload = e => {
+        var data = e.target.result
+        var mdata = stox(XLSX.read(data, { type: 'array' }))
+
+        if (mdata) {
+          /* update x-spreadsheet */
+          this.spreadsheetObj.loadData(mdata)
+          this.importDialogVisible = false
+        }
+      }
+      reader.readAsArrayBuffer(f)
+    }
+  },
+  mounted() {
+    this.menu = this.item_info.menu
+    this.page_id = this.menu.pages[0].page_id
+
+    // 加载依赖""
+    $s([`static/xspreadsheet/xspreadsheet.js`], () => {
+      $s(
+        [
+          `static/xspreadsheet/locale/zh-cn.js`,
+          `static/xspreadsheet/locale/en.js`
+        ],
+        () => {
+          if (DocConfig.lang == 'en') {
+            x_spreadsheet.locale('en')
+          } else {
+            x_spreadsheet.locale('zh-cn')
+          }
+          this.getPageContent()
+
+          if (this.item_info.ItemPermn) {
+            this.remoteIsLock()
+          }
+        }
+      )
+      $s([`static/xspreadsheet/xlsx.full.min.js`])
+    })
+  },
+  beforeDestroy() {
+    this.$message.closeAll()
+    clearInterval(this.intervalId)
+    this.unlock()
+  }
+}
+</script>

+ 12 - 7
web_src/src/components/page/edit/Index.vue

@@ -269,6 +269,8 @@ export default {
               } else {
                 that.editor_watch()
               }
+              // 开启草稿
+              that.draft()
             }, 1000)
             that.title = response.data.data.page_title
             that.item_id = response.data.data.item_id
@@ -341,9 +343,9 @@ export default {
     insert_api_template() {
       var val
       if (DocConfig.lang == 'zh-cn') {
-        val = apiTemplateZh ;
+        val = apiTemplateZh
       } else {
-        val = apiTemplateEn ;
+        val = apiTemplateEn
       }
       this.insertValue(val)
     },
@@ -352,9 +354,9 @@ export default {
     insert_database_template() {
       var val
       if (DocConfig.lang == 'zh-cn') {
-        val = databaseTemplateZh ;
+        val = databaseTemplateZh
       } else {
-        val = databaseTemplateEn ;
+        val = databaseTemplateEn
       }
       this.insertValue(val)
     },
@@ -561,16 +563,20 @@ export default {
     draft() {
       var that = this
       var pkey = 'page_content_' + this.page_id
+      let childRef = this.$refs.Editormd
       // 定时保存文本内容到localStorage
       setInterval(() => {
-        let childRef = this.$refs.Editormd
         var content = childRef.getMarkdown()
         localStorage.setItem(pkey, content)
       }, 30 * 1000)
 
       // 检测是否有定时保存的内容
       var page_content = localStorage.getItem(pkey)
-      if (page_content && page_content.length > 0) {
+      if (
+        page_content &&
+        page_content.length > 0 &&
+        page_content != childRef.getMarkdown()
+      ) {
         localStorage.removeItem(pkey)
         that
           .$confirm(that.$t('draft_tips'), '', {
@@ -664,7 +670,6 @@ export default {
     }
     this.get_catalog(this.$route.params.item_id)
 
-    this.draft()
     this.heartBeatLock()
     this.remoteIsLock()
     /** 监听粘贴上传图片 **/

+ 4 - 1
web_src/static/lang/en.js

@@ -370,5 +370,8 @@ exports.default = {
   c_team: 'Select team',
 
   Logged: 'Logged',
-  update_pwd_tips: 'Password, leave blank if not changed'
+  update_pwd_tips: 'Password, leave blank if not changed',
+
+  import_excel: 'You can choose to import excel file',
+  table: 'table'
 }

+ 4 - 1
web_src/static/lang/zh-CN.js

@@ -361,5 +361,8 @@ exports.default = {
   c_team: '选择团队',
 
   Logged: '已登录',
-  update_pwd_tips: '密码,不修改则留空'
+  update_pwd_tips: '密码,不修改则留空',
+
+  import_excel: '你可以选择Excel文件导入',
+  table: '表格'
 }

File diff suppressed because it is too large
+ 17 - 0
web_src/static/xspreadsheet/58eaeb4e52248a5c75936c6f4c33a370.svg


File diff suppressed because it is too large
+ 17 - 0
web_src/static/xspreadsheet/ece3e4fa05d4292823fdef970eaf1233.svg


File diff suppressed because it is too large
+ 0 - 0
web_src/static/xspreadsheet/locale/de.js


File diff suppressed because it is too large
+ 0 - 0
web_src/static/xspreadsheet/locale/en.js


File diff suppressed because it is too large
+ 0 - 0
web_src/static/xspreadsheet/locale/nl.js


File diff suppressed because it is too large
+ 0 - 0
web_src/static/xspreadsheet/locale/zh-cn.js


File diff suppressed because it is too large
+ 1 - 0
web_src/static/xspreadsheet/xlsx.full.min.js


+ 1038 - 0
web_src/static/xspreadsheet/xspreadsheet.css

@@ -0,0 +1,1038 @@
+body {
+  margin: 0;
+}
+.x-spreadsheet {
+  font-size: 13px;
+  line-height: normal;
+  user-select: none;
+  -moz-user-select: none;
+  font-family: 'Lato', 'Source Sans Pro', Roboto, Helvetica, Arial, sans-serif;
+  box-sizing: content-box;
+  background: #fff;
+  -webkit-font-smoothing: antialiased;
+}
+.x-spreadsheet textarea {
+  font: 400 13px Arial, 'Lato', 'Source Sans Pro', Roboto, Helvetica, sans-serif;
+}
+.x-spreadsheet-sheet {
+  position: relative;
+  overflow: hidden;
+}
+.x-spreadsheet-table {
+  vertical-align: bottom;
+}
+.x-spreadsheet-tooltip {
+  font-family: inherit;
+  position: absolute;
+  padding: 5px 10px;
+  color: #fff;
+  border-radius: 1px;
+  background: #000000;
+  font-size: 12px;
+  z-index: 201;
+}
+.x-spreadsheet-tooltip:before {
+  pointer-events: none;
+  position: absolute;
+  left: calc(50% - 4px);
+  top: -4px;
+  content: "";
+  width: 8px;
+  height: 8px;
+  background: inherit;
+  -webkit-transform: rotate(45deg);
+  transform: rotate(45deg);
+  z-index: 1;
+  box-shadow: 1px 1px 3px -1px rgba(0, 0, 0, 0.3);
+}
+.x-spreadsheet-color-palette {
+  padding: 5px;
+}
+.x-spreadsheet-color-palette table {
+  margin: 0;
+  padding: 0;
+  border-collapse: separate;
+  border-spacing: 2;
+  background: #fff;
+}
+.x-spreadsheet-color-palette table td {
+  margin: 0;
+  cursor: pointer;
+  border: 1px solid transparent;
+}
+.x-spreadsheet-color-palette table td:hover {
+  border-color: #ddd;
+}
+.x-spreadsheet-color-palette table td .x-spreadsheet-color-palette-cell {
+  width: 16px;
+  height: 16px;
+}
+.x-spreadsheet-border-palette {
+  padding: 6px;
+}
+.x-spreadsheet-border-palette table {
+  margin: 0;
+  padding: 0;
+  border-collapse: separate;
+  border-spacing: 0;
+  background: #fff;
+  table-layout: fixed;
+}
+.x-spreadsheet-border-palette table td {
+  margin: 0;
+}
+.x-spreadsheet-border-palette .x-spreadsheet-border-palette-left {
+  border-right: 1px solid #eee;
+  padding-right: 6px;
+}
+.x-spreadsheet-border-palette .x-spreadsheet-border-palette-left .x-spreadsheet-border-palette-cell {
+  width: 30px;
+  height: 30px;
+  cursor: pointer;
+  text-align: center;
+}
+.x-spreadsheet-border-palette .x-spreadsheet-border-palette-left .x-spreadsheet-border-palette-cell .x-spreadsheet-icon-img {
+  opacity: 0.8;
+}
+.x-spreadsheet-border-palette .x-spreadsheet-border-palette-left .x-spreadsheet-border-palette-cell:hover {
+  background-color: #eee;
+}
+.x-spreadsheet-border-palette .x-spreadsheet-border-palette-right {
+  padding-left: 6px;
+}
+.x-spreadsheet-border-palette .x-spreadsheet-border-palette-right .x-spreadsheet-toolbar-btn {
+  margin-top: 0;
+  margin-bottom: 3px;
+}
+.x-spreadsheet-border-palette .x-spreadsheet-border-palette-right .x-spreadsheet-line-type {
+  position: relative;
+  left: 0;
+  top: -3px;
+}
+.x-spreadsheet-dropdown {
+  position: relative;
+}
+.x-spreadsheet-dropdown .x-spreadsheet-dropdown-content {
+  position: absolute;
+  z-index: 200;
+  background: #fff;
+  box-shadow: 1px 2px 5px 2px rgba(51, 51, 51, 0.15);
+}
+.x-spreadsheet-dropdown.bottom-left .x-spreadsheet-dropdown-content {
+  top: calc(100% + 5px);
+  left: 0;
+}
+.x-spreadsheet-dropdown.bottom-right .x-spreadsheet-dropdown-content {
+  top: calc(100% + 5px);
+  right: 0;
+}
+.x-spreadsheet-dropdown.top-left .x-spreadsheet-dropdown-content {
+  bottom: calc(100% + 5px);
+  left: 0;
+}
+.x-spreadsheet-dropdown.top-right .x-spreadsheet-dropdown-content {
+  bottom: calc(100% + 5px);
+  right: 0;
+}
+.x-spreadsheet-dropdown .x-spreadsheet-dropdown-title {
+  padding: 0 5px;
+  display: inline-block;
+}
+.x-spreadsheet-dropdown .x-spreadsheet-dropdown-header .x-spreadsheet-icon.arrow-left {
+  margin-left: 4px;
+}
+.x-spreadsheet-dropdown .x-spreadsheet-dropdown-header .x-spreadsheet-icon.arrow-right {
+  width: 10px;
+  margin-right: 4px;
+}
+.x-spreadsheet-dropdown .x-spreadsheet-dropdown-header .x-spreadsheet-icon.arrow-right .arrow-down {
+  left: -130px;
+}
+/* resizer **/
+.x-spreadsheet-resizer {
+  position: absolute;
+  z-index: 11;
+}
+.x-spreadsheet-resizer .x-spreadsheet-resizer-hover {
+  background-color: rgba(75, 137, 255, 0.25);
+}
+.x-spreadsheet-resizer .x-spreadsheet-resizer-line {
+  position: absolute;
+}
+.x-spreadsheet-resizer.horizontal {
+  cursor: row-resize;
+}
+.x-spreadsheet-resizer.horizontal .x-spreadsheet-resizer-line {
+  border-bottom: 2px dashed #4b89ff;
+  left: 0;
+  bottom: 0;
+}
+.x-spreadsheet-resizer.vertical {
+  cursor: col-resize;
+}
+.x-spreadsheet-resizer.vertical .x-spreadsheet-resizer-line {
+  border-right: 2px dashed #4b89ff;
+  top: 0;
+  right: 0;
+}
+/* scrollbar */
+.x-spreadsheet-scrollbar {
+  position: absolute;
+  bottom: 0;
+  right: 0;
+  background-color: #f4f5f8;
+  opacity: 0.9;
+  z-index: 12;
+}
+.x-spreadsheet-scrollbar.horizontal {
+  right: 15px;
+  overflow-x: scroll;
+  overflow-y: hidden;
+}
+.x-spreadsheet-scrollbar.horizontal > div {
+  height: 1px;
+  background: #ddd;
+}
+.x-spreadsheet-scrollbar.vertical {
+  bottom: 15px;
+  overflow-x: hidden;
+  overflow-y: scroll;
+}
+.x-spreadsheet-scrollbar.vertical > div {
+  width: 1px;
+  background: #ddd;
+}
+/* @{css-prefix}-overlayer */
+.x-spreadsheet-overlayer {
+  position: absolute;
+  left: 0;
+  top: 0;
+  z-index: 10;
+}
+.x-spreadsheet-overlayer .x-spreadsheet-overlayer-content {
+  position: absolute;
+  overflow: hidden;
+  pointer-events: none;
+  width: 100%;
+  height: 100%;
+}
+.x-spreadsheet-editor,
+.x-spreadsheet-selector {
+  box-sizing: content-box;
+  position: absolute;
+  overflow: hidden;
+  pointer-events: none;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+}
+/* @{css-prefix}-selector */
+.x-spreadsheet-selector .hide-input {
+  position: absolute;
+  z-index: 0;
+}
+.x-spreadsheet-selector .hide-input input {
+  width: 0;
+  border: none;
+}
+.x-spreadsheet-selector .x-spreadsheet-selector-area {
+  position: absolute;
+  border: 2px solid #4b89ff;
+  background: rgba(75, 137, 255, 0.1);
+  z-index: 5;
+}
+.x-spreadsheet-selector .x-spreadsheet-selector-clipboard,
+.x-spreadsheet-selector .x-spreadsheet-selector-autofill {
+  position: absolute;
+  background: transparent;
+  z-index: 100;
+}
+.x-spreadsheet-selector .x-spreadsheet-selector-clipboard {
+  border: 2px dashed #4b89ff;
+}
+.x-spreadsheet-selector .x-spreadsheet-selector-autofill {
+  border: 1px dashed rgba(0, 0, 0, 0.45);
+}
+.x-spreadsheet-selector .x-spreadsheet-selector-corner {
+  pointer-events: auto;
+  position: absolute;
+  cursor: crosshair;
+  font-size: 0;
+  height: 5px;
+  width: 5px;
+  right: -5px;
+  bottom: -5px;
+  border: 2px solid #ffffff;
+  background: #4b89ff;
+}
+.x-spreadsheet-editor {
+  z-index: 20;
+}
+.x-spreadsheet-editor .x-spreadsheet-editor-area {
+  position: absolute;
+  text-align: left;
+  border: 2px solid #4b89ff;
+  line-height: 0;
+  z-index: 100;
+  pointer-events: auto;
+}
+.x-spreadsheet-editor .x-spreadsheet-editor-area textarea {
+  box-sizing: content-box;
+  border: none;
+  padding: 0 3px;
+  outline: none;
+  resize: none;
+  text-align: start;
+  overflow-y: hidden;
+  font: 400 13px Arial, 'Lato', 'Source Sans Pro', Roboto, Helvetica, sans-serif;
+  color: inherit;
+  white-space: normal;
+  word-wrap: break-word;
+  line-height: 22px;
+  margin: 0;
+}
+.x-spreadsheet-editor .x-spreadsheet-editor-area .textline {
+  overflow: hidden;
+  visibility: hidden;
+  position: fixed;
+  top: 0;
+  left: 0;
+}
+.x-spreadsheet-item {
+  user-select: none;
+  background: 0;
+  border: 1px solid transparent;
+  outline: none;
+  height: 26px;
+  color: rgba(0, 0, 0, 0.9);
+  line-height: 26px;
+  list-style: none;
+  padding: 2px 10px;
+  cursor: default;
+  text-align: left;
+  overflow: hidden;
+}
+.x-spreadsheet-item.disabled {
+  pointer-events: none;
+  opacity: 0.5;
+}
+.x-spreadsheet-item:hover,
+.x-spreadsheet-item.active {
+  background: rgba(0, 0, 0, 0.05);
+}
+.x-spreadsheet-item.divider {
+  height: 0;
+  padding: 0;
+  margin: 5px 0;
+  border: none;
+  border-bottom: 1px solid rgba(0, 0, 0, 0.1);
+}
+.x-spreadsheet-item .label {
+  float: right;
+  opacity: 0.65;
+  font-size: 1em;
+}
+.x-spreadsheet-item.state,
+.x-spreadsheet-header.state {
+  padding-left: 35px!important;
+  position: relative;
+}
+.x-spreadsheet-item.state:before,
+.x-spreadsheet-header.state:before {
+  content: '';
+  position: absolute;
+  width: 10px;
+  height: 10px;
+  left: 12px;
+  top: calc(50% - 5px);
+  background: rgba(0, 0, 0, 0.08);
+  border-radius: 2px;
+}
+.x-spreadsheet-item.state.checked:before,
+.x-spreadsheet-header.state.checked:before {
+  background: #4b89ff;
+}
+.x-spreadsheet-checkbox {
+  position: relative;
+  display: inline-block;
+  backface-visibility: hidden;
+  outline: 0;
+  vertical-align: baseline;
+  font-style: normal;
+  font-size: 1rem;
+  line-height: 1em;
+}
+.x-spreadsheet-checkbox > input {
+  position: absolute;
+  top: 0;
+  left: 0;
+  opacity: 0!important;
+  outline: 0;
+  z-index: -1;
+}
+.x-spreadsheet-suggest,
+.x-spreadsheet-contextmenu,
+.x-spreadsheet-sort-filter {
+  position: absolute;
+  box-shadow: 1px 2px 5px 2px rgba(51, 51, 51, 0.15);
+  background: #fff;
+  z-index: 100;
+  width: 260px;
+  pointer-events: auto;
+  overflow: auto;
+}
+.x-spreadsheet-suggest {
+  width: 200px;
+}
+.x-spreadsheet-filter {
+  border: 1px solid #e9e9e9;
+  font-size: 12px;
+  margin: 10px;
+}
+.x-spreadsheet-filter .x-spreadsheet-header {
+  padding: 0.5em 0.75em;
+  background: #f8f8f9;
+  border-bottom: 1px solid #e9e9e9;
+  border-left: 1px solid transparent;
+}
+.x-spreadsheet-filter .x-spreadsheet-body {
+  height: 200px;
+  overflow-y: auto;
+}
+.x-spreadsheet-filter .x-spreadsheet-body .x-spreadsheet-item {
+  height: 20px;
+  line-height: 20px;
+}
+.x-spreadsheet-sort-filter .x-spreadsheet-buttons {
+  margin: 10px;
+}
+.x-spreadsheet-toolbar,
+.x-spreadsheet-bottombar {
+  height: 40px;
+  padding: 0 30px;
+  text-align: left;
+  background: #f5f6f7;
+  display: flex;
+}
+.x-spreadsheet-bottombar {
+  position: relative;
+  border-top: 1px solid #e0e2e4;
+}
+.x-spreadsheet-bottombar .x-spreadsheet-menu > li {
+  line-height: 40px;
+  height: 40px;
+  padding-top: 0;
+  padding-bottom: 0;
+  vertical-align: middle;
+  border-right: 1px solid #e8eaed;
+}
+.x-spreadsheet-menu {
+  list-style: none;
+  margin: 0;
+  padding: 0;
+  user-select: none;
+}
+.x-spreadsheet-menu > li {
+  float: left;
+  line-height: 1.25em;
+  padding: 0.785em 1em;
+  margin: 0;
+  vertical-align: middle;
+  text-align: left;
+  font-weight: 400;
+  color: #80868b;
+  white-space: nowrap;
+  cursor: pointer;
+  transition: all 0.3s;
+  font-weight: bold;
+}
+.x-spreadsheet-menu > li.active {
+  background-color: #fff;
+  color: rgba(0, 0, 0, 0.65);
+}
+.x-spreadsheet-menu > li .x-spreadsheet-icon {
+  margin: 0 6px;
+}
+.x-spreadsheet-menu > li .x-spreadsheet-icon .x-spreadsheet-icon-img:hover {
+  opacity: 0.85;
+}
+.x-spreadsheet-menu > li .x-spreadsheet-dropdown {
+  display: inline-block;
+}
+.x-spreadsheet-toolbar {
+  border-bottom: 1px solid #e0e2e4;
+}
+.x-spreadsheet-toolbar .x-spreadsheet-toolbar-btns {
+  display: inline-flex;
+}
+.x-spreadsheet-toolbar .x-spreadsheet-toolbar-more {
+  padding: 0 6px 6px;
+  text-align: left;
+}
+.x-spreadsheet-toolbar .x-spreadsheet-toolbar-more .x-spreadsheet-toolbar-divider {
+  margin-top: 0;
+}
+.x-spreadsheet-toolbar .x-spreadsheet-toolbar-btn {
+  flex: 0 0 auto;
+  display: inline-block;
+  border: 1px solid transparent;
+  height: 26px;
+  line-height: 26px;
+  min-width: 26px;
+  margin: 6px 1px 0;
+  padding: 0;
+  text-align: center;
+  border-radius: 2px;
+}
+.x-spreadsheet-toolbar .x-spreadsheet-toolbar-btn.disabled {
+  pointer-events: none;
+  opacity: 0.5;
+}
+.x-spreadsheet-toolbar .x-spreadsheet-toolbar-btn:hover,
+.x-spreadsheet-toolbar .x-spreadsheet-toolbar-btn.active {
+  background: rgba(0, 0, 0, 0.08);
+}
+.x-spreadsheet-toolbar-divider {
+  display: inline-block;
+  border-right: 1px solid #e0e2e4;
+  width: 0;
+  vertical-align: middle;
+  height: 18px;
+  margin: 12px 3px 0;
+}
+.x-spreadsheet-print {
+  position: absolute;
+  left: 0;
+  top: 0;
+  z-index: 100;
+  width: 100%;
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+}
+.x-spreadsheet-print-bar {
+  background: #424242;
+  height: 60px;
+  line-height: 60px;
+  padding: 0 30px;
+}
+.x-spreadsheet-print-bar .-title {
+  color: #fff;
+  font-weight: bold;
+  font-size: 1.2em;
+  float: left;
+}
+.x-spreadsheet-print-bar .-right {
+  float: right;
+  margin-top: 12px;
+}
+.x-spreadsheet-print-content {
+  display: flex;
+  flex: auto;
+  flex-direction: row;
+  background: #d0d0d0;
+  height: calc(100% - 60px);
+}
+.x-spreadsheet-print-content .-sider {
+  flex: 0 0 300px;
+  width: 300px;
+  border-left: 2px solid #ccc;
+  background: #fff;
+}
+.x-spreadsheet-print-content .-content {
+  flex: auto;
+  overflow-x: auto;
+  overflow-y: scroll;
+  height: 100%;
+}
+.x-spreadsheet-canvas-card-wraper {
+  margin: 40px 20px;
+}
+.x-spreadsheet-canvas-card {
+  background: #fff;
+  margin: auto;
+  page-break-before: auto;
+  page-break-after: always;
+  box-shadow: 0 8px 10px 1px rgba(0, 0, 0, 0.14), 0 3px 14px 3px rgba(0, 0, 0, 0.12), 0 4px 5px 0 rgba(0, 0, 0, 0.2);
+}
+.x-spreadsheet-calendar {
+  color: rgba(0, 0, 0, 0.65);
+  background: #ffffff;
+  user-select: none;
+}
+.x-spreadsheet-calendar .calendar-header {
+  font-weight: 700;
+  line-height: 30px;
+  text-align: center;
+  width: 100%;
+  float: left;
+  background: #f9fafb;
+}
+.x-spreadsheet-calendar .calendar-header .calendar-header-left {
+  padding-left: 5px;
+  float: left;
+}
+.x-spreadsheet-calendar .calendar-header .calendar-header-right {
+  float: right;
+}
+.x-spreadsheet-calendar .calendar-header .calendar-header-right a {
+  padding: 3px 0;
+  margin-right: 2px;
+  border-radius: 2px;
+}
+.x-spreadsheet-calendar .calendar-header .calendar-header-right a:hover {
+  background: rgba(0, 0, 0, 0.08);
+}
+.x-spreadsheet-calendar .calendar-body {
+  border-collapse: collapse;
+  border-spacing: 0;
+}
+.x-spreadsheet-calendar .calendar-body th,
+.x-spreadsheet-calendar .calendar-body td {
+  width: 14.28571429%;
+  min-width: 32px;
+  text-align: center;
+  font-weight: 700;
+  line-height: 30px;
+  padding: 0;
+}
+.x-spreadsheet-calendar .calendar-body td > .cell:hover {
+  background: #ecf6fd;
+}
+.x-spreadsheet-calendar .calendar-body td > .cell.active,
+.x-spreadsheet-calendar .calendar-body td > .cell.active:hover {
+  background: #ecf6fd;
+  color: #2185D0;
+}
+.x-spreadsheet-calendar .calendar-body td > .cell.disabled {
+  pointer-events: none;
+  opacity: 0.5;
+}
+.x-spreadsheet-datepicker {
+  box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.2);
+  position: absolute;
+  left: 0;
+  top: calc(100% + 5px);
+  z-index: 10;
+  width: auto;
+}
+.x-spreadsheet-buttons {
+  display: flex;
+  justify-content: flex-end;
+}
+.x-spreadsheet-buttons .x-spreadsheet-button {
+  margin-left: 8px;
+}
+.x-spreadsheet-button {
+  display: inline-block;
+  border-radius: 3px;
+  line-height: 1em;
+  min-height: 1em;
+  white-space: nowrap;
+  text-align: center;
+  cursor: pointer;
+  font-size: 1em;
+  font-weight: 700;
+  padding: 0.75em 1em;
+  color: rgba(0, 0, 0, 0.6);
+  background: #E0E1E2;
+  text-decoration: none;
+  font-family: "Lato", "proxima-nova", "Helvetica Neue", Arial, sans-serif;
+  outline: none;
+  vertical-align: baseline;
+  zoom: 1;
+  user-select: none;
+  transition: all 0.1s linear;
+}
+.x-spreadsheet-button.active,
+.x-spreadsheet-button:hover {
+  background-color: #C0C1C2;
+  color: rgba(0, 0, 0, 0.8);
+}
+.x-spreadsheet-button.primary {
+  color: #fff;
+  background-color: #2185D0;
+}
+.x-spreadsheet-button.primary:hover,
+.x-spreadsheet-button.primary.active {
+  color: #fff;
+  background-color: #1678c2;
+}
+.x-spreadsheet-form-input {
+  font-size: 1em;
+  position: relative;
+  font-weight: 400;
+  display: inline-flex;
+  color: rgba(0, 0, 0, 0.87);
+}
+.x-spreadsheet-form-input input {
+  z-index: 1;
+  margin: 0;
+  max-width: 100%;
+  flex: 1 0 auto;
+  outline: 0;
+  -webkit-tap-highlight-color: rgba(255, 255, 255, 0);
+  text-align: left;
+  line-height: 30px;
+  height: 30px;
+  padding: 0 8px;
+  background: #fff;
+  border: 1px solid #e9e9e9;
+  border-radius: 3px;
+  transition: box-shadow 0.1s ease, border-color 0.1s ease;
+  box-shadow: inset 0 1px 2px hsla(0, 0%, 4%, 0.06);
+}
+.x-spreadsheet-form-input input:focus {
+  border-color: #4b89ff;
+  box-shadow: inset 0 1px 2px rgba(75, 137, 255, 0.2);
+}
+.x-spreadsheet-form-select {
+  position: relative;
+  display: inline-block;
+  background: #fff;
+  border: 1px solid #e9e9e9;
+  border-radius: 2px;
+  cursor: pointer;
+  color: rgba(0, 0, 0, 0.87);
+  user-select: none;
+  box-shadow: inset 0 1px 2px hsla(0, 0%, 4%, 0.06);
+}
+.x-spreadsheet-form-select .input-text {
+  text-overflow: ellipsis;
+  white-space: nowrap;
+  min-width: 60px;
+  width: auto;
+  height: 30px;
+  line-height: 30px;
+  padding: 0 8px;
+}
+.x-spreadsheet-form-fields {
+  display: flex;
+  flex-direction: row;
+  flex-wrap: wrap;
+}
+.x-spreadsheet-form-fields .x-spreadsheet-form-field {
+  flex: 0 1 auto;
+}
+.x-spreadsheet-form-fields .x-spreadsheet-form-field .label {
+  display: inline-block;
+  margin: 0 10px 0 0;
+}
+.x-spreadsheet-form-field {
+  display: block;
+  vertical-align: middle;
+  margin-left: 10px;
+  margin-bottom: 10px;
+}
+.x-spreadsheet-form-field:first-child {
+  margin-left: 0;
+}
+.x-spreadsheet-form-field.error .x-spreadsheet-form-select,
+.x-spreadsheet-form-field.error input {
+  border-color: #f04134;
+}
+.x-spreadsheet-form-field .tip {
+  color: #f04134;
+  font-size: 0.9em;
+}
+.x-spreadsheet-dimmer {
+  display: none;
+  position: absolute;
+  top: 0 !important;
+  left: 0 !important;
+  width: 100%;
+  height: 100%;
+  text-align: center;
+  vertical-align: middle;
+  background-color: rgba(0, 0, 0, 0.6);
+  opacity: 0;
+  -webkit-animation-fill-mode: both;
+  animation-fill-mode: both;
+  -webkit-animation-duration: 0.5s;
+  animation-duration: 0.5s;
+  transition: background-color 0.5s linear;
+  user-select: none;
+  z-index: 1000;
+}
+.x-spreadsheet-dimmer.active {
+  display: block;
+  opacity: 1;
+}
+form fieldset {
+  border: none;
+}
+form fieldset label {
+  display: block;
+  margin-bottom: 0.5em;
+  font-size: 1em;
+  color: #666;
+}
+form fieldset select {
+  font-size: 1.1em;
+  width: 100%;
+  background-color: #fff;
+  border: none;
+  border-bottom: 2px solid #ddd;
+  padding: 0.5em 0.85em;
+  border-radius: 2px;
+}
+.x-spreadsheet-modal,
+.x-spreadsheet-toast {
+  font-size: 13px;
+  position: fixed;
+  z-index: 1001;
+  text-align: left;
+  line-height: 1.25em;
+  min-width: 360px;
+  color: rgba(0, 0, 0, 0.87);
+  font-family: 'Lato', 'Source Sans Pro', Roboto, Helvetica, Arial, sans-serif;
+  border-radius: 4px;
+  border: 1px solid rgba(0, 0, 0, 0.1);
+  background-color: #fff;
+  background-clip: padding-box;
+  box-shadow: rgba(0, 0, 0, 0.2) 0px 2px 8px;
+}
+.x-spreadsheet-toast {
+  background-color: rgba(255, 255, 255, 0.85);
+}
+.x-spreadsheet-modal-header,
+.x-spreadsheet-toast-header {
+  font-weight: 600;
+  background-clip: padding-box;
+  background-color: rgba(255, 255, 255, 0.85);
+  border-bottom: 1px solid rgba(0, 0, 0, 0.05);
+  border-radius: 4px 4px 0 0;
+}
+.x-spreadsheet-modal-header .x-spreadsheet-icon,
+.x-spreadsheet-toast-header .x-spreadsheet-icon {
+  position: absolute;
+  right: 0.8em;
+  top: 0.65em;
+  border-radius: 18px;
+}
+.x-spreadsheet-modal-header .x-spreadsheet-icon:hover,
+.x-spreadsheet-toast-header .x-spreadsheet-icon:hover {
+  opacity: 1;
+  background: rgba(0, 0, 0, 0.08);
+}
+.x-spreadsheet-toast-header {
+  color: #F2711C;
+}
+.x-spreadsheet-modal-header {
+  border-bottom: 1px solid #e0e2e4;
+  background: rgba(0, 0, 0, 0.08);
+  font-size: 1.0785em;
+}
+.x-spreadsheet-modal-header,
+.x-spreadsheet-modal-content,
+.x-spreadsheet-toast-header,
+.x-spreadsheet-toast-content {
+  padding: 0.75em 1em;
+}
+@media screen and (min-width: 320px) and (max-width: 480px) {
+  .x-spreadsheet-toolbar {
+    display: none;
+  }
+}
+.x-spreadsheet-icon {
+  width: 18px;
+  height: 18px;
+  margin: 1px 1px 2px 1px;
+  text-align: center;
+  vertical-align: middle;
+  user-select: none;
+  overflow: hidden;
+  position: relative;
+  display: inline-block;
+}
+.x-spreadsheet-icon .x-spreadsheet-icon-img {
+  background-image: url(58eaeb4e52248a5c75936c6f4c33a370.svg);
+  position: absolute;
+  width: 262px;
+  height: 444px;
+  opacity: 0.56;
+}
+.x-spreadsheet-icon .x-spreadsheet-icon-img.undo {
+  left: 0;
+  top: 0;
+}
+.x-spreadsheet-icon .x-spreadsheet-icon-img.redo {
+  left: -18px;
+  top: 0;
+}
+.x-spreadsheet-icon .x-spreadsheet-icon-img.print {
+  left: -36px;
+  top: 0;
+}
+.x-spreadsheet-icon .x-spreadsheet-icon-img.paintformat {
+  left: -54px;
+  top: 0;
+}
+.x-spreadsheet-icon .x-spreadsheet-icon-img.clearformat {
+  left: -72px;
+  top: 0;
+}
+.x-spreadsheet-icon .x-spreadsheet-icon-img.font-bold {
+  left: -90px;
+  top: 0;
+}
+.x-spreadsheet-icon .x-spreadsheet-icon-img.font-italic {
+  left: -108px;
+  top: 0;
+}
+.x-spreadsheet-icon .x-spreadsheet-icon-img.underline {
+  left: -126px;
+  top: 0;
+}
+.x-spreadsheet-icon .x-spreadsheet-icon-img.strike {
+  left: -144px;
+  top: 0;
+}
+.x-spreadsheet-icon .x-spreadsheet-icon-img.color {
+  left: -162px;
+  top: 0;
+}
+.x-spreadsheet-icon .x-spreadsheet-icon-img.bgcolor {
+  left: -180px;
+  top: 0;
+}
+.x-spreadsheet-icon .x-spreadsheet-icon-img.merge {
+  left: -198px;
+  top: 0;
+}
+.x-spreadsheet-icon .x-spreadsheet-icon-img.align-left {
+  left: -216px;
+  top: 0;
+}
+.x-spreadsheet-icon .x-spreadsheet-icon-img.align-center {
+  left: -234px;
+  top: 0;
+}
+.x-spreadsheet-icon .x-spreadsheet-icon-img.align-right {
+  left: 0;
+  top: -18px;
+}
+.x-spreadsheet-icon .x-spreadsheet-icon-img.align-top {
+  left: -18px;
+  top: -18px;
+}
+.x-spreadsheet-icon .x-spreadsheet-icon-img.align-middle {
+  left: -36px;
+  top: -18px;
+}
+.x-spreadsheet-icon .x-spreadsheet-icon-img.align-bottom {
+  left: -54px;
+  top: -18px;
+}
+.x-spreadsheet-icon .x-spreadsheet-icon-img.textwrap {
+  left: -72px;
+  top: -18px;
+}
+.x-spreadsheet-icon .x-spreadsheet-icon-img.autofilter {
+  left: -90px;
+  top: -18px;
+}
+.x-spreadsheet-icon .x-spreadsheet-icon-img.formula {
+  left: -108px;
+  top: -18px;
+}
+.x-spreadsheet-icon .x-spreadsheet-icon-img.arrow-down {
+  left: -126px;
+  top: -18px;
+}
+.x-spreadsheet-icon .x-spreadsheet-icon-img.arrow-right {
+  left: -144px;
+  top: -18px;
+}
+.x-spreadsheet-icon .x-spreadsheet-icon-img.link {
+  left: -162px;
+  top: -18px;
+}
+.x-spreadsheet-icon .x-spreadsheet-icon-img.chart {
+  left: -180px;
+  top: -18px;
+}
+.x-spreadsheet-icon .x-spreadsheet-icon-img.freeze {
+  left: -198px;
+  top: -18px;
+}
+.x-spreadsheet-icon .x-spreadsheet-icon-img.ellipsis {
+  left: -216px;
+  top: -18px;
+}
+.x-spreadsheet-icon .x-spreadsheet-icon-img.add {
+  left: -234px;
+  top: -18px;
+}
+.x-spreadsheet-icon .x-spreadsheet-icon-img.border-all {
+  left: 0;
+  top: -36px;
+}
+.x-spreadsheet-icon .x-spreadsheet-icon-img.border-inside {
+  left: -18px;
+  top: -36px;
+}
+.x-spreadsheet-icon .x-spreadsheet-icon-img.border-horizontal {
+  left: -36px;
+  top: -36px;
+}
+.x-spreadsheet-icon .x-spreadsheet-icon-img.border-vertical {
+  left: -54px;
+  top: -36px;
+}
+.x-spreadsheet-icon .x-spreadsheet-icon-img.border-outside {
+  left: -72px;
+  top: -36px;
+}
+.x-spreadsheet-icon .x-spreadsheet-icon-img.border-left {
+  left: -90px;
+  top: -36px;
+}
+.x-spreadsheet-icon .x-spreadsheet-icon-img.border-top {
+  left: -108px;
+  top: -36px;
+}
+.x-spreadsheet-icon .x-spreadsheet-icon-img.border-right {
+  left: -126px;
+  top: -36px;
+}
+.x-spreadsheet-icon .x-spreadsheet-icon-img.border-bottom {
+  left: -144px;
+  top: -36px;
+}
+.x-spreadsheet-icon .x-spreadsheet-icon-img.border-none {
+  left: -162px;
+  top: -36px;
+}
+.x-spreadsheet-icon .x-spreadsheet-icon-img.line-color {
+  left: -180px;
+  top: -36px;
+}
+.x-spreadsheet-icon .x-spreadsheet-icon-img.line-type {
+  left: -198px;
+  top: -36px;
+}
+.x-spreadsheet-icon .x-spreadsheet-icon-img.close {
+  left: -234px;
+  top: -36px;
+}
+.x-spreadsheet-icon .x-spreadsheet-icon-img.chevron-down {
+  left: 0;
+  top: -54px;
+}
+.x-spreadsheet-icon .x-spreadsheet-icon-img.chevron-up {
+  left: -18px;
+  top: -54px;
+}
+.x-spreadsheet-icon .x-spreadsheet-icon-img.chevron-left {
+  left: -36px;
+  top: -54px;
+}
+.x-spreadsheet-icon .x-spreadsheet-icon-img.chevron-right {
+  left: -54px;
+  top: -54px;
+}
+
+
+/*# sourceMappingURL=xspreadsheet.css.map*/

File diff suppressed because it is too large
+ 0 - 0
web_src/static/xspreadsheet/xspreadsheet.js


Some files were not shown because too many files changed in this diff