├── .eslintignore ├── commitlint.config.js ├── src ├── css │ ├── EwaAntH.gif │ ├── EwaAntV.gif │ ├── loading.gif │ ├── arrow-down.png │ ├── paint_16px.ico │ ├── paint_24px.ico │ ├── paint_32px.ico │ ├── waffle_sprite.png │ ├── luckysheet-print.css │ ├── luckysheet-cellFormat.css │ ├── iconCustom.css │ ├── luckysheet-zoom.css │ └── luckysheet-protection.css ├── fonts │ ├── FontAwesome.otf │ ├── fontawesome-webfont.eot │ ├── fontawesome-webfont.ttf │ ├── fontawesome-webfont.woff │ └── fontawesome-webfont.woff2 ├── plugins │ ├── images │ │ ├── js.png │ │ ├── CFicons.png │ │ ├── CFdataBar.png │ │ ├── icon_dropCell.png │ │ ├── CFcolorGradation.png │ │ ├── ui-icons_444444_256x240.png │ │ ├── ui-icons_555555_256x240.png │ │ ├── ui-icons_777620_256x240.png │ │ ├── ui-icons_777777_256x240.png │ │ ├── ui-icons_cc0000_256x240.png │ │ └── ui-icons_ffffff_256x240.png │ ├── js │ │ └── jquery.mousewheel.min.js │ └── jquery.sPage.css ├── assets │ └── iconfont │ │ ├── iconfont.eot │ │ ├── iconfont.ttf │ │ ├── iconfont.woff │ │ ├── iconfont.woff2 │ │ ├── Anton-Regular.ttf │ │ ├── Pacifico-Regular.ttf │ │ └── HanaleiFill-Regular.ttf ├── global │ ├── loading.js │ ├── json.js │ ├── cleargridelement.js │ ├── analysis.js │ ├── createsheet.js │ ├── datecontroll.js │ ├── count.js │ ├── array.js │ ├── location.js │ ├── rhchInit.js │ ├── scroll.js │ ├── dynamicArray.js │ ├── cursorPos.js │ ├── createdom.js │ ├── editor.js │ ├── validate.js │ ├── browser.js │ └── setdata.js ├── locale │ └── locale.js ├── function │ ├── luckysheet_function.js │ └── functionlist.js ├── controllers │ ├── expendPlugins.js │ ├── imageUpdateCtrl.js │ ├── luckysheetConfigsetting.js │ ├── listener.js │ ├── sheetSearch.js │ ├── print.js │ ├── cellDatePickerCtrl.js │ ├── cellFormat.js │ └── mobile.js ├── index.js ├── expendPlugins │ ├── print │ │ └── plugin.js │ └── chart │ │ └── chartmix.css ├── methods │ ├── set.js │ └── get.js ├── demoData │ ├── sheetComment.js │ ├── demoFeature.js │ └── sheetPivotTable.js ├── utils │ ├── polyfill.js │ ├── chartUtil.js │ └── math.js ├── config.js └── store │ └── index.js ├── docs ├── .vuepress │ ├── public │ │ ├── favicon.ico │ │ └── img │ │ │ ├── QQ群二维码.jpg │ │ │ ├── excel.png │ │ │ ├── logo.png │ │ │ ├── 微信二维码.jpg │ │ │ ├── logo_text.png │ │ │ └── LuckysheetDemo.gif │ └── config.js ├── zh │ ├── README.md │ ├── about │ │ ├── README.md │ │ ├── company.md │ │ └── sponsor.md │ └── guide │ │ ├── resource.md │ │ └── contribute.md ├── README.md ├── about │ ├── README.md │ ├── company.md │ └── sponsor.md └── guide │ └── resource.md ├── .gitignore ├── .github ├── ISSUE_TEMPLATE │ ├── feature_request_zh.md │ ├── bug_report_zh.md │ ├── feature_request.md │ └── bug_report.md ├── FUNDING.yml └── workflows │ ├── github-demo.yml │ ├── github-doc.yml │ └── gitee-mirror.yml ├── LICENSE ├── package.json ├── deploy.bat └── README.md /.eslintignore: -------------------------------------------------------------------------------- 1 | src 2 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@commitlint/config-conventional'] 3 | } 4 | -------------------------------------------------------------------------------- /src/css/EwaAntH.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blue-BigTech/GoogleSheet-Lucky/HEAD/src/css/EwaAntH.gif -------------------------------------------------------------------------------- /src/css/EwaAntV.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blue-BigTech/GoogleSheet-Lucky/HEAD/src/css/EwaAntV.gif -------------------------------------------------------------------------------- /src/css/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blue-BigTech/GoogleSheet-Lucky/HEAD/src/css/loading.gif -------------------------------------------------------------------------------- /src/css/arrow-down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blue-BigTech/GoogleSheet-Lucky/HEAD/src/css/arrow-down.png -------------------------------------------------------------------------------- /src/css/paint_16px.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blue-BigTech/GoogleSheet-Lucky/HEAD/src/css/paint_16px.ico -------------------------------------------------------------------------------- /src/css/paint_24px.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blue-BigTech/GoogleSheet-Lucky/HEAD/src/css/paint_24px.ico -------------------------------------------------------------------------------- /src/css/paint_32px.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blue-BigTech/GoogleSheet-Lucky/HEAD/src/css/paint_32px.ico -------------------------------------------------------------------------------- /src/css/waffle_sprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blue-BigTech/GoogleSheet-Lucky/HEAD/src/css/waffle_sprite.png -------------------------------------------------------------------------------- /src/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blue-BigTech/GoogleSheet-Lucky/HEAD/src/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /src/plugins/images/js.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blue-BigTech/GoogleSheet-Lucky/HEAD/src/plugins/images/js.png -------------------------------------------------------------------------------- /src/plugins/images/CFicons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blue-BigTech/GoogleSheet-Lucky/HEAD/src/plugins/images/CFicons.png -------------------------------------------------------------------------------- /docs/.vuepress/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blue-BigTech/GoogleSheet-Lucky/HEAD/docs/.vuepress/public/favicon.ico -------------------------------------------------------------------------------- /src/assets/iconfont/iconfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blue-BigTech/GoogleSheet-Lucky/HEAD/src/assets/iconfont/iconfont.eot -------------------------------------------------------------------------------- /src/assets/iconfont/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blue-BigTech/GoogleSheet-Lucky/HEAD/src/assets/iconfont/iconfont.ttf -------------------------------------------------------------------------------- /src/assets/iconfont/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blue-BigTech/GoogleSheet-Lucky/HEAD/src/assets/iconfont/iconfont.woff -------------------------------------------------------------------------------- /src/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blue-BigTech/GoogleSheet-Lucky/HEAD/src/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /src/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blue-BigTech/GoogleSheet-Lucky/HEAD/src/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /src/plugins/images/CFdataBar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blue-BigTech/GoogleSheet-Lucky/HEAD/src/plugins/images/CFdataBar.png -------------------------------------------------------------------------------- /docs/.vuepress/public/img/QQ群二维码.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blue-BigTech/GoogleSheet-Lucky/HEAD/docs/.vuepress/public/img/QQ群二维码.jpg -------------------------------------------------------------------------------- /docs/.vuepress/public/img/excel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blue-BigTech/GoogleSheet-Lucky/HEAD/docs/.vuepress/public/img/excel.png -------------------------------------------------------------------------------- /docs/.vuepress/public/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blue-BigTech/GoogleSheet-Lucky/HEAD/docs/.vuepress/public/img/logo.png -------------------------------------------------------------------------------- /docs/.vuepress/public/img/微信二维码.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blue-BigTech/GoogleSheet-Lucky/HEAD/docs/.vuepress/public/img/微信二维码.jpg -------------------------------------------------------------------------------- /src/assets/iconfont/iconfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blue-BigTech/GoogleSheet-Lucky/HEAD/src/assets/iconfont/iconfont.woff2 -------------------------------------------------------------------------------- /src/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blue-BigTech/GoogleSheet-Lucky/HEAD/src/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /src/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blue-BigTech/GoogleSheet-Lucky/HEAD/src/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /src/plugins/images/icon_dropCell.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blue-BigTech/GoogleSheet-Lucky/HEAD/src/plugins/images/icon_dropCell.png -------------------------------------------------------------------------------- /src/assets/iconfont/Anton-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blue-BigTech/GoogleSheet-Lucky/HEAD/src/assets/iconfont/Anton-Regular.ttf -------------------------------------------------------------------------------- /docs/.vuepress/public/img/logo_text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blue-BigTech/GoogleSheet-Lucky/HEAD/docs/.vuepress/public/img/logo_text.png -------------------------------------------------------------------------------- /src/assets/iconfont/Pacifico-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blue-BigTech/GoogleSheet-Lucky/HEAD/src/assets/iconfont/Pacifico-Regular.ttf -------------------------------------------------------------------------------- /src/plugins/images/CFcolorGradation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blue-BigTech/GoogleSheet-Lucky/HEAD/src/plugins/images/CFcolorGradation.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | package-lock.json 4 | dist 5 | docs/.vuepress/dist 6 | 7 | .idea 8 | .history 9 | .vs 10 | .vscode -------------------------------------------------------------------------------- /src/assets/iconfont/HanaleiFill-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blue-BigTech/GoogleSheet-Lucky/HEAD/src/assets/iconfont/HanaleiFill-Regular.ttf -------------------------------------------------------------------------------- /docs/.vuepress/public/img/LuckysheetDemo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blue-BigTech/GoogleSheet-Lucky/HEAD/docs/.vuepress/public/img/LuckysheetDemo.gif -------------------------------------------------------------------------------- /src/plugins/images/ui-icons_444444_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blue-BigTech/GoogleSheet-Lucky/HEAD/src/plugins/images/ui-icons_444444_256x240.png -------------------------------------------------------------------------------- /src/plugins/images/ui-icons_555555_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blue-BigTech/GoogleSheet-Lucky/HEAD/src/plugins/images/ui-icons_555555_256x240.png -------------------------------------------------------------------------------- /src/plugins/images/ui-icons_777620_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blue-BigTech/GoogleSheet-Lucky/HEAD/src/plugins/images/ui-icons_777620_256x240.png -------------------------------------------------------------------------------- /src/plugins/images/ui-icons_777777_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blue-BigTech/GoogleSheet-Lucky/HEAD/src/plugins/images/ui-icons_777777_256x240.png -------------------------------------------------------------------------------- /src/plugins/images/ui-icons_cc0000_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blue-BigTech/GoogleSheet-Lucky/HEAD/src/plugins/images/ui-icons_cc0000_256x240.png -------------------------------------------------------------------------------- /src/plugins/images/ui-icons_ffffff_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blue-BigTech/GoogleSheet-Lucky/HEAD/src/plugins/images/ui-icons_ffffff_256x240.png -------------------------------------------------------------------------------- /src/global/loading.js: -------------------------------------------------------------------------------- 1 | export function showloading(txt) { 2 | $("#luckysheet-cell-loading").find("span").text(txt).end().show(); 3 | }; 4 | 5 | export function hideloading() { 6 | $("#luckysheet-cell-loading").hide(); 7 | }; -------------------------------------------------------------------------------- /src/locale/locale.js: -------------------------------------------------------------------------------- 1 | import en from './en' 2 | import zh from './zh' 3 | import es from './es' 4 | import zh_tw from './zh_tw' 5 | import Store from '../store'; 6 | 7 | export const locales = {en,zh,es,zh_tw} 8 | 9 | function locale(){ 10 | return locales[Store.lang]; 11 | } 12 | 13 | export default locale; -------------------------------------------------------------------------------- /docs/zh/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | home: true 3 | heroText: Luckysheet 4 | tagline: 配置文档 · API · 教程 5 | actionText: 快速上手 → 6 | actionLink: /zh/guide/ 7 | features: 8 | - title: 功能强大 9 | details: 包含大量常用电子表格功能,替代你的excel 10 | - title: 配置简单 11 | details: 最少的配置就能开始上手使用 12 | - title: 完全开源 13 | details: 社区驱动,共同来完善你的想法 14 | footer: MIT Licensed | Copyright © 2020-present Mengshukeji 15 | --- -------------------------------------------------------------------------------- /src/function/luckysheet_function.js: -------------------------------------------------------------------------------- 1 | import functionlist from './functionlist'; 2 | 3 | const luckysheet_function = {}; 4 | 5 | for (let i = 0; i < functionlist.length; i++) { 6 | let func = functionlist[i]; 7 | luckysheet_function[func.n] = func; 8 | } 9 | 10 | window.luckysheet_function = luckysheet_function; //挂载window 用于 eval() 计算公式 11 | 12 | export default luckysheet_function; -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request_zh.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 功能要求 3 | about: 为这个项目提出想法 4 | title: "[Feature request]我有个点子" 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | **您的功能请求与问题有关吗?** 17 | 18 | 19 | 20 | **描述您想要的解决方案** 21 | 22 | 23 | **其他内容** 24 | 25 | -------------------------------------------------------------------------------- /src/controllers/expendPlugins.js: -------------------------------------------------------------------------------- 1 | import { chart } from '../expendPlugins/chart/plugin' 2 | import { print } from '../expendPlugins/print/plugin' 3 | 4 | const pluginsObj = { 5 | 'chart':chart, 6 | 'print':print 7 | } 8 | 9 | const isDemo = true 10 | 11 | /** 12 | * Register plugins 13 | */ 14 | function initPlugins(plugins , data){ 15 | if(plugins.length){ 16 | plugins.forEach(plugin => { 17 | pluginsObj[plugin](data , isDemo) 18 | }); 19 | } 20 | } 21 | 22 | export { 23 | initPlugins 24 | } -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import './utils/math' 2 | import { luckysheet } from './core' 3 | import __firefox from './utils/polyfill' 4 | // Prevent gulp warning: 'Use of eval is strongly discouraged, as it poses security risks and may cause issues with minification' 5 | // window.evall = window.eval; 6 | // polyfill event in firefox 7 | if(window.addEventListener && (navigator.userAgent.indexOf("Firefox") > 0)){ 8 | __firefox(); 9 | } 10 | 11 | // export default luckysheet; 12 | 13 | // use esbuild,bundle iife format 14 | module.exports = luckysheet -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | home: true 3 | heroText: Luckysheet 4 | tagline: Configuration Document · API · Tutorial 5 | actionText: Get Started → 6 | actionLink: /guide/ 7 | features: 8 | - title: Powerful Features 9 | details: Contains a large number of commonly used spreadsheet functions to replace your excel 10 | - title: Simple Configuration 11 | details: Get started with minimal configuration 12 | - title: Open Source 13 | details: Community driven, work together to improve your ideas 14 | footer: MIT Licensed | Copyright © 2020-present Mengshukeji 15 | --- -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # [mengshukeji] 4 | patreon: mengshukeji 5 | open_collective: luckysheet 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: ['https://www.paypal.me/wbfsa'] 13 | -------------------------------------------------------------------------------- /src/expendPlugins/print/plugin.js: -------------------------------------------------------------------------------- 1 | import { seriesLoadScripts, loadLinks, $$ } from '../../utils/util' 2 | 3 | 4 | // Dynamically load dependent scripts and styles 5 | const dependScripts = [ 6 | // 'expendPlugins/chart/chartmix.umd.min.js', 7 | 'http://localhost:8080/luckysheetPluginPrint.umd.js', 8 | ] 9 | 10 | const dependLinks = [ 11 | // 'expendPlugins/chart/chartmix.css', 12 | 'http://localhost:8080/luckysheetPluginPrint.css', 13 | ] 14 | 15 | // Initialize the chart component 16 | function print(data, isDemo) { 17 | loadLinks(dependLinks); 18 | 19 | seriesLoadScripts(dependScripts, null, function () { 20 | 21 | }); 22 | } 23 | 24 | 25 | 26 | export { print } 27 | -------------------------------------------------------------------------------- /src/controllers/imageUpdateCtrl.js: -------------------------------------------------------------------------------- 1 | // 自定义图片的更新方法例如: customImageUpdate("POST", "http://127.0.0.1:8000/luckysheetimageprocess/", d) 2 | function customImageUpdate(method, url, obj) { 3 | return new Promise((resolve, reject) => { 4 | const xhr = new XMLHttpRequest() || new ActiveXObject("Microsoft.XMLHTTP"); 5 | xhr.open(method, url); 6 | xhr.send(JSON.stringify(obj)); // 发送 POST/GET 数据 7 | xhr.onreadystatechange = function () { 8 | if (xhr.readyState == 4) { 9 | if (xhr.status == 200) { 10 | resolve(xhr.responseText); 11 | } else { 12 | reject("error"); 13 | } 14 | } 15 | }; 16 | }); 17 | } 18 | 19 | export { 20 | customImageUpdate 21 | } -------------------------------------------------------------------------------- /src/global/json.js: -------------------------------------------------------------------------------- 1 | import { getObjType } from '../utils/util'; 2 | 3 | const json = { 4 | parseJsonParm: function(obj){ 5 | if(obj == null){ 6 | return {}; 7 | } 8 | else if(getObjType(obj) == "string"){ 9 | try { 10 | let json = new Function("return " + obj)(); 11 | return json; 12 | } 13 | catch(e) { 14 | return {}; 15 | } 16 | } 17 | else{ 18 | return obj; 19 | } 20 | }, 21 | hasKey: function(obj){ 22 | let _this = this; 23 | let json = _this.parseJsonParm(obj); 24 | 25 | for(let item in json){ 26 | return true; 27 | } 28 | 29 | return false; 30 | } 31 | } 32 | 33 | export default json; -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report_zh.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: '错误报告' 3 | about: 创建报告帮助我们改进 4 | title: '[BUG]发现了个bug' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | **描述错误** 17 | 18 | 19 | **重现** 20 | 21 | 1. 第一步操作: 22 | 2. 第二步操作: 23 | 3. 第三步操作: 24 | 4. 最后看到了什么错误: 25 | 26 | **期望的结果** 27 | 28 | 29 | **屏幕截图或演示** 30 | 31 | 32 | **环境:** 33 | - 操作系统: 34 | - 浏览器 版本号: 35 | - Luckysheet版本: 36 | 37 | **备注** 38 | 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "[Feature request]I have an idea" 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | **Is your feature request related to a problem? Please describe.** 17 | 18 | 19 | **Describe the solution you'd like** 20 | 21 | 22 | **Additional context** 23 | 24 | -------------------------------------------------------------------------------- /.github/workflows/github-demo.yml: -------------------------------------------------------------------------------- 1 | name: Luckysheet demo github pages deploy 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | jobs: 9 | publish: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout # 检查项目 13 | uses: actions/checkout@v2 14 | 15 | - name: Set up Node.js # Nodejs版本 16 | uses: actions/setup-node@master 17 | with: 18 | node-version: 12.13.0 19 | 20 | - name: Install dependencies & Generate static files # 安装依赖打包demo和文档 21 | run: | 22 | node -v 23 | npm install 24 | npm install gulp -g 25 | npm run build 26 | 27 | - name: Deploy LuckysheetDemo to GitHub Pages # 发布demo 到github pages 28 | if: success() 29 | uses: crazy-max/ghaction-github-pages@v2 30 | with: 31 | repo: mengshukeji/LuckysheetDemo 32 | target_branch: gh-pages 33 | build_dir: dist 34 | env: 35 | GITHUB_TOKEN: ${{ secrets.GIT_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/github-doc.yml: -------------------------------------------------------------------------------- 1 | name: Luckysheet docs github pages deploy 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'doc*' 7 | 8 | jobs: 9 | publish: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout # 检查项目 13 | uses: actions/checkout@v2 14 | 15 | - name: Set up Node.js # Nodejs版本 16 | uses: actions/setup-node@master 17 | with: 18 | node-version: 12.13.0 19 | 20 | - name: Install dependencies & Generate static files # 安装依赖打包demo和文档 21 | run: | 22 | node -v 23 | npm install 24 | npm install gulp -g 25 | npm run docs:build 26 | 27 | - name: Deploy LuckysheetDocs to GitHub Pages # 发布docs 到github pages 28 | if: success() 29 | uses: crazy-max/ghaction-github-pages@v2 30 | with: 31 | repo: mengshukeji/LuckysheetDocs 32 | target_branch: gh-pages 33 | build_dir: docs/.vuepress/dist 34 | env: 35 | GITHUB_TOKEN: ${{ secrets.GIT_TOKEN }} -------------------------------------------------------------------------------- /docs/zh/about/README.md: -------------------------------------------------------------------------------- 1 | # 认识团队 2 | 3 | Luckysheet是由个人主导、几个志同道合的小伙伴一同开发的项目。 4 | 5 | ## 核心团队活跃成员 6 | - [@wbfsa](https://github.com/wbfsa) 7 | - [@eiji-th](https://github.com/eiji-th) 8 | - [@fly-95](https://github.com/fly-95) 9 | - [@tonytonychopper123](https://github.com/tonytonychopper123) 10 | - [@Dushusir](https://github.com/Dushusir) 11 | - [@iamxuchen800117](https://github.com/iamxuchen800117) 12 | - [@wpxp123456](https://github.com/wpxp123456) 13 | - [@c19c19i](https://weibo.com/u/3884623955) 14 | - [@zhangchen915](https://github.com/zhangchen915) 15 | - [@jerry-f](https://github.com/jerry-f) 16 | - [@flowerField](https://github.com/flowerField) 17 | 18 | ## 社区伙伴 19 | - [@yiwasheng](https://github.com/yiwasheng) 20 | - [@danielcai1987](https://github.com/danielcai1987) 21 | - [@qq6690876](https://github.com/qq6690876) 22 | - [@javahuang](https://github.com/javahuang) 23 | - [@TimerGang](https://github.com/TimerGang) 24 | - [@gsw945](https://github.com/gsw945) 25 | - [@swen-xiong](https://github.com/swen-xiong) 26 | - [@lzmch](https://github.com/lzmch) 27 | - [@kdevilpf](https://github.com/kdevilpf) 28 | - [@WJWM0316](https://github.com/WJWM0316) -------------------------------------------------------------------------------- /src/controllers/luckysheetConfigsetting.js: -------------------------------------------------------------------------------- 1 | const luckysheetConfigsetting = { 2 | autoFormatw: false, 3 | accuracy: undefined, 4 | total: 0, 5 | 6 | allowCopy: true, 7 | showtoolbar: true, 8 | showinfobar: true, 9 | showsheetbar: true, 10 | showstatisticBar: true, 11 | pointEdit: false, 12 | pointEditUpdate: null, 13 | pointEditZoom: 1, 14 | 15 | userInfo: false, 16 | userMenuItem: [], 17 | myFolderUrl: null, 18 | functionButton: null, 19 | 20 | showConfigWindowResize: true, 21 | enableAddRow: true, 22 | addRowCount: 100, 23 | enableAddBackTop: true, 24 | enablePage: true, 25 | pageInfo: null, 26 | 27 | 28 | editMode: false, 29 | beforeCreateDom: null, 30 | workbookCreateBefore: null, 31 | workbookCreateAfter: null, 32 | remoteFunction: null, 33 | fireMousedown: null, 34 | plugins:[], 35 | forceCalculation:false,//强制刷新公式,公式较多会有性能问题,慎用 36 | 37 | defaultColWidth:73, 38 | defaultRowHeight:19, 39 | 40 | defaultTextColor: '#000', 41 | defaultCellColor: '#fff', 42 | } 43 | 44 | export default luckysheetConfigsetting; 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020-present, Mengshukeji 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /src/css/luckysheet-print.css: -------------------------------------------------------------------------------- 1 | .luckysheet-print-viewList{ 2 | position: relative; 3 | float: right; 4 | width:126px; 5 | /* right: 222px; */ 6 | height: 22px; 7 | line-height: 22px; 8 | text-align: center; 9 | white-space: nowrap; 10 | overflow: hidden; 11 | display: flex; 12 | align-items: center; 13 | user-select: none; 14 | } 15 | 16 | .luckysheet-print-viewBtn { 17 | position: absolute; 18 | top: 0; 19 | left: 0px; 20 | width: 42px; 21 | height:22px; 22 | align-items: center; 23 | justify-content: center; 24 | cursor: pointer; 25 | } 26 | 27 | .luckysheet-print-viewBtn .iconfont{ 28 | font-size: 22px; 29 | } 30 | 31 | .luckysheet-print-viewBtn:hover{ 32 | background: #E1E4E8; 33 | } 34 | 35 | .luckysheet-print-viewBtn-active{ 36 | background: #dcdcdc; 37 | cursor: default; 38 | } 39 | 40 | .luckysheet-print-viewBtn-active:hover{ 41 | background: #dcdcdc; 42 | } 43 | 44 | .luckysheet-print-viewNormal{ 45 | left: 0px; 46 | } 47 | 48 | .luckysheet-print-viewLayout{ 49 | left: 42px; 50 | } 51 | 52 | .luckysheet-print-viewPage{ 53 | left: 84px; 54 | } 55 | 56 | -------------------------------------------------------------------------------- /docs/about/README.md: -------------------------------------------------------------------------------- 1 | # Meet the Team 2 | 3 | Luckysheet is a project led by an individual and jointly developed by several friends with the same interests. 4 | 5 | ### Active Core Team Members 6 | - [@wbfsa](https://github.com/wbfsa) 7 | - [@eiji-th](https://github.com/eiji-th) 8 | - [@fly-95](https://github.com/fly-95) 9 | - [@tonytonychopper123](https://github.com/tonytonychopper123) 10 | - [@Dushusir](https://github.com/Dushusir) 11 | - [@iamxuchen800117](https://github.com/iamxuchen800117) 12 | - [@wpxp123456](https://github.com/wpxp123456) 13 | - [@c19c19i](https://weibo.com/u/3884623955) 14 | - [@zhangchen915](https://github.com/zhangchen915) 15 | - [@jerry-f](https://github.com/jerry-f) 16 | - [@flowerField](https://github.com/flowerField) 17 | 18 | ### Community Partners 19 | - [@yiwasheng](https://github.com/yiwasheng) 20 | - [@danielcai1987](https://github.com/danielcai1987) 21 | - [@qq6690876](https://github.com/qq6690876) 22 | - [@javahuang](https://github.com/javahuang) 23 | - [@TimerGang](https://github.com/TimerGang) 24 | - [@gsw945](https://github.com/gsw945) 25 | - [@swen-xiong](https://github.com/swen-xiong) 26 | - [@lzmch](https://github.com/lzmch) 27 | - [@kdevilpf](https://github.com/kdevilpf) 28 | - [@WJWM0316](https://github.com/WJWM0316) -------------------------------------------------------------------------------- /src/methods/set.js: -------------------------------------------------------------------------------- 1 | import { getSheetIndex } from '../methods/get'; 2 | import Store from '../store'; 3 | 4 | function setluckysheet_select_save(v) { 5 | Store.luckysheet_select_save = v; 6 | } 7 | 8 | function setluckysheet_scroll_status(v) { 9 | Store.luckysheet_scroll_status = v; 10 | } 11 | 12 | function setluckysheetfile(d) { 13 | Store.luckysheetfile = d; 14 | } 15 | 16 | function setconfig(v) { 17 | Store.config = v; 18 | 19 | if(Store.luckysheetfile != null){ 20 | Store.luckysheetfile[getSheetIndex(Store.currentSheetIndex)].config = v; 21 | } 22 | } 23 | 24 | function setvisibledatarow(v) { 25 | Store.visibledatarow = v; 26 | 27 | if(Store.luckysheetfile != null){ 28 | Store.luckysheetfile[getSheetIndex(Store.currentSheetIndex)].visibledatarow = v; 29 | } 30 | } 31 | 32 | function setvisibledatacolumn(v) { 33 | Store.visibledatacolumn = v; 34 | 35 | if(Store.luckysheetfile != null){ 36 | Store.luckysheetfile[getSheetIndex(Store.currentSheetIndex)].visibledatacolumn = v; 37 | } 38 | } 39 | 40 | export { 41 | setluckysheet_select_save, 42 | setluckysheet_scroll_status, 43 | setluckysheetfile, 44 | setconfig, 45 | setvisibledatarow, 46 | setvisibledatacolumn, 47 | } -------------------------------------------------------------------------------- /src/global/cleargridelement.js: -------------------------------------------------------------------------------- 1 | import selection from '../controllers/selection'; 2 | import menuButton from '../controllers/menuButton'; 3 | 4 | export default function cleargridelement(event) { 5 | $("#luckysheet-cols-h-hover").hide(); 6 | $("#luckysheet-rightclick-menu").hide(); 7 | 8 | $("#luckysheet-cell-selected-boxs .luckysheet-cell-selected").hide(); 9 | $("#luckysheet-cols-h-selected .luckysheet-cols-h-selected").hide(); 10 | $("#luckysheet-rows-h-selected .luckysheet-rows-h-selected").hide(); 11 | 12 | $("#luckysheet-cell-selected-focus").hide(); 13 | $("#luckysheet-rows-h-hover").hide(); 14 | $("#luckysheet-selection-copy .luckysheet-selection-copy").hide(); 15 | $("#luckysheet-cols-menu-btn").hide(); 16 | $("#luckysheet-row-count-show, #luckysheet-column-count-show").hide(); 17 | if (!event) { 18 | selection.clearcopy(event); 19 | } 20 | //else{ 21 | // selection.clearcopy(); 22 | //} 23 | 24 | //选区下拉icon隐藏 25 | if($("#luckysheet-dropCell-icon").is(":visible")){ 26 | if(event){ 27 | $("#luckysheet-dropCell-icon").remove(); 28 | } 29 | } 30 | //格式刷 31 | if(menuButton.luckysheetPaintModelOn && !event){ 32 | menuButton.cancelPaintModel(); 33 | } 34 | } -------------------------------------------------------------------------------- /src/demoData/sheetComment.js: -------------------------------------------------------------------------------- 1 | window.sheetComment = { 2 | "name": "Comment", 3 | "color": "", 4 | "config": { 5 | "columnlen": { 6 | "2": 102 7 | } 8 | }, 9 | "index": "5", 10 | "chart": [], 11 | "status": 0, 12 | "order": "5", 13 | "column": 18, 14 | "row": 36, 15 | "celldata": [{ 16 | "r": 2, 17 | "c": 2, 18 | "v": { 19 | "m": "HoverShown", 20 | "ct": { 21 | "fa": "General", 22 | "t": "g" 23 | }, 24 | "v": "HoverShown", 25 | "bl": 1, 26 | "ps": { 27 | "left": null, 28 | "top": null, 29 | "width": null, 30 | "height": null, 31 | "value": "Hello world!", 32 | "isshow": false 33 | } 34 | } 35 | }, { 36 | "r": 7, 37 | "c": 2, 38 | "v": { 39 | "m": "Size", 40 | "ct": { 41 | "fa": "General", 42 | "t": "g" 43 | }, 44 | "v": "Size", 45 | "bl": 1, 46 | "ps": { 47 | "left": null, 48 | "top": null, 49 | "width": null, 50 | "height": null, 51 | "value": "Hello,world!", 52 | "isshow": true 53 | } 54 | } 55 | }], 56 | "ch_width": 4748, 57 | "rh_height": 1790, 58 | "luckysheet_select_save": [{ 59 | "row": [0, 0], 60 | "column": [0, 0] 61 | }], 62 | "luckysheet_selection_range": [], 63 | "scrollLeft": 0, 64 | "scrollTop": 0 65 | } 66 | 67 | // export default sheetComment; -------------------------------------------------------------------------------- /src/global/analysis.js: -------------------------------------------------------------------------------- 1 | import { numFormat } from '../utils/util'; 2 | 3 | const analysis = { 4 | "STDEVP": function (mean, array1d) { 5 | let cov = 0; 6 | for (let i = 0; i < array1d.length; i++) { 7 | let xi = array1d[i]; 8 | cov += Math.pow(xi - mean, 2); 9 | } 10 | return numFormat(Math.sqrt(cov / array1d.length)); 11 | }, 12 | "STDEV": function (mean, array1d) { 13 | let cov = 0; 14 | for (let i = 0; i < array1d.length; i++) { 15 | let xi = array1d[i]; 16 | cov += Math.pow(xi - mean, 2); 17 | } 18 | return numFormat(Math.sqrt(cov / (array1d.length - 1))); 19 | }, 20 | "VARP": function (mean, array1d) { 21 | let cov = 0; 22 | for (let i = 0; i < array1d.length; i++) { 23 | let xi = array1d[i]; 24 | cov += Math.pow(xi - mean, 2); 25 | } 26 | return numFormat(cov / array1d.length); 27 | }, 28 | "let": function (mean, array1d) { 29 | let cov = 0; 30 | for (let i = 0; i < array1d.length; i++) { 31 | let xi = array1d[i]; 32 | cov += Math.pow(xi - mean, 2); 33 | } 34 | return numFormat(cov / (array1d.length - 1)); 35 | }, 36 | }; 37 | 38 | export default analysis; -------------------------------------------------------------------------------- /src/utils/polyfill.js: -------------------------------------------------------------------------------- 1 | /** 2 | * polyfill event in firefox 3 | */ 4 | function __firefox(){ 5 | HTMLElement.prototype.__defineGetter__("runtimeStyle", __element_style); 6 | window.constructor.prototype.__defineGetter__("event", __window_event); 7 | Event.prototype.__defineGetter__("srcElement", __event_srcElement); 8 | } 9 | 10 | function __element_style(){ 11 | return this.style; 12 | } 13 | 14 | function __window_event(){ 15 | return __window_event_constructor(); 16 | } 17 | 18 | function __event_srcElement(){ 19 | return this.target; 20 | } 21 | 22 | function __window_event_constructor(){ 23 | if(document.all){ 24 | return window.event; 25 | } 26 | 27 | var _caller = __window_event_constructor.caller; 28 | 29 | while(_caller != null){ 30 | var _argument = _caller.arguments[0]; 31 | 32 | if(_argument){ 33 | var _temp = _argument.constructor; 34 | 35 | if(_temp.toString().indexOf("Event") != -1){ 36 | return _argument; 37 | } 38 | } 39 | 40 | _caller = _caller.caller; 41 | } 42 | 43 | return null; 44 | } 45 | 46 | export default __firefox; -------------------------------------------------------------------------------- /src/function/functionlist.js: -------------------------------------------------------------------------------- 1 | import functionImplementation from './functionImplementation'; 2 | import Store from '../store/index' 3 | import locale from '../locale/locale'; 4 | //{"0":"数学","1":"统计","2":"查找","3":"Luckysheet内置","4":"数据挖掘","5":"数据源","6":"日期","7":"过滤器","8":"财务","9":"工程计算","10":"逻辑","11":"运算符","12":"文本","13":"转换工具","14":"数组"} 5 | 6 | const functionlist = function(customFunctions){ 7 | let _locale = locale(); 8 | // internationalization,get function list 9 | let functionListOrigin = [..._locale.functionlist]; 10 | 11 | // add new property f 12 | for (let i = 0; i < functionListOrigin.length; i++) { 13 | let func = functionListOrigin[i]; 14 | func.f = functionImplementation[func.n]; 15 | } 16 | 17 | if (customFunctions) { 18 | functionListOrigin.push(...customFunctions); 19 | } 20 | 21 | Store.functionlist = functionListOrigin; 22 | 23 | // get n property 24 | const luckysheet_function = {}; 25 | 26 | for (let i = 0; i < functionListOrigin.length; i++) { 27 | let func = functionListOrigin[i]; 28 | luckysheet_function[func.n] = func; 29 | } 30 | 31 | window.luckysheet_function = luckysheet_function; //Mount window for eval() calculation formula 32 | 33 | Store.luckysheet_function = luckysheet_function; 34 | } 35 | 36 | export default functionlist; 37 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[BUG]Find a bug" 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | **Describe the bug** 17 | 18 | 19 | **To Reproduce** 20 | 21 | 1. The first step: 22 | 2. The second step: 23 | 3. The third step: 24 | 4. See error: 25 | 26 | **What is expected?** 27 | 28 | 29 | **Screenshots or demo** 30 | 31 | 32 | **Environment** 33 | - OS: 34 | - Browser Version: 35 | - Luckysheet Version: 36 | 37 | **Additional context** 38 | 39 | -------------------------------------------------------------------------------- /src/demoData/demoFeature.js: -------------------------------------------------------------------------------- 1 | 2 | // Features specially written for demo 3 | 4 | (function() { 5 | 6 | // language 7 | function language(params) { 8 | 9 | var lang = navigator.language||navigator.userLanguage;//常规浏览器语言和IE浏览器 10 | lang = lang.substr(0, 2);//截取lang前2位字符 11 | 12 | return lang; 13 | 14 | } 15 | // Tencent Forum Link Button 16 | function supportButton() { 17 | const text = language() === 'zh' ? '反馈' : 'Forum'; 18 | const link = language() === 'zh' ? 'https://support.qq.com/product/288322' : 'https://groups.google.com/g/luckysheet'; 19 | 20 | document.querySelector("body").insertAdjacentHTML('beforeend', ''+ text +''); 21 | } 22 | 23 | supportButton() 24 | 25 | /** 26 | * Get url parameters 27 | */ 28 | function getRequest() { 29 | var vars = {}; 30 | var parts = window.location.href.replace(/[?&]+([^=&]+)=([^&]*)/gi, 31 | function(m,key,value) { 32 | vars[key] = value; 33 | }); 34 | return vars; 35 | } 36 | 37 | window.luckysheetDemoUtil = { 38 | language:language, 39 | getRequest:getRequest 40 | } 41 | 42 | })() -------------------------------------------------------------------------------- /docs/zh/guide/resource.md: -------------------------------------------------------------------------------- 1 | # 教程与资源 2 | 3 | 开源软件离不开社区的贡献,这里将会列举出社区提供的教程、学习资料及配套解决方案。 4 | 5 | 如果您写了或者发现了优秀的教程想要推荐给我们,请直接[编辑此页](https://github.com/mengshukeji/Luckysheet/edit/master/docs/zh/guide/resource.md)提交PR。 6 | 7 | ## 博客 8 | - [Luckysheet如何初始化含合并单元格的数据](https://www.cnblogs.com/DuShuSir/p/13272397.html) 9 | - [Luckysheet如何把表格里的数据保存到数据库](https://www.cnblogs.com/DuShuSir/p/13857874.html) 10 | - [本地HTML采用cdn加载方式引入Luckysheet的案例](https://www.cnblogs.com/DuShuSir/p/13859103.html) 11 | - [Luckysheet基础用法,使用loadUrl加载服务端数据](https://blog.csdn.net/DCDC2020/article/details/108486525) 12 | - [Luckysheet 导入与导出实现 - Java后台处理](https://blog.csdn.net/u014632228/article/details/109738221) 13 | 14 | ## 前端案例 15 | 16 | ### 社区案例 17 | - [luckysheet-vue-importAndExport](https://github.com/oy-paddy/luckysheet-vue-importAndExport/tree/master/) 18 | 19 | ## 后端案例 20 | 21 | ### 官方案例 22 | - [Java 后台 Luckysheet Server](https://github.com/mengshukeji/LuckysheetServer) 23 | 24 | ### 社区案例 25 | - [Luckysheet保存与恢复](https://gitee.com/ichiva/luckysheet-saved-in-recovery)(Java版) 26 | - [基于Luckysheet实现的协同编辑在线表格](https://github.com/DilemmaVi/ecsheet)(Java版) 27 | - [使用.net core 3.1和Npoi 制作基于LuckSheet的基础导出](https://gitee.com/xiong-kangli/luck-sheet_.-net-core)(.NET 版本) 28 | - [go语言版本的协同编辑](https://github.com/fandypeng/excel2config)(Go 版本) 29 | 30 | ## 学习资料 31 | 32 | - [如何从0到1搭建 Web 数据分析报表](https://github.com/mengshukeji/LuckyResources/blob/master/ppt/%E5%A6%82%E4%BD%95%E4%BB%8E0%E5%88%B01%E6%90%AD%E5%BB%BA%20Web%20%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90%E6%8A%A5%E8%A1%A8.pptx) -------------------------------------------------------------------------------- /src/css/luckysheet-cellFormat.css: -------------------------------------------------------------------------------- 1 | .luckysheet-cellFormat-config{ 2 | display: none; 3 | } 4 | 5 | .luckysheet-cellFormat-config .luckysheet-modal-dialog-content{ 6 | position: relative; 7 | height: 550px; 8 | width: 600px; 9 | } 10 | 11 | .luckysheet-cellFormat-menu-c{ 12 | position: absolute; 13 | width: 100%; 14 | height: 30px; 15 | border-right: 1px solid #fff; 16 | border-bottom: 1px solid #d4d4d4; 17 | font-size: 12px; 18 | } 19 | 20 | .luckysheet-cellFormat-menu{ 21 | position: relative; 22 | display: inline-block; 23 | height: 30px; 24 | width: 80px; 25 | text-align: center; 26 | line-height: 30px; 27 | border: 1px solid #d4d4d4; 28 | border-bottom: none; 29 | background: #F0F0F0; 30 | cursor: pointer; 31 | } 32 | 33 | .luckysheet-cellFormat-menu:hover{ 34 | background: #e7e7e7; 35 | } 36 | 37 | 38 | .luckysheet-cellFormat-menu-active{ 39 | background: #fff; 40 | cursor: default; 41 | } 42 | 43 | .luckysheet-cellFormat-menu-active:hover{ 44 | background: #fff; 45 | } 46 | 47 | 48 | .luckysheet-cellFormat-content{ 49 | position: absolute; 50 | top:30px; 51 | bottom: 0px; 52 | width: 100%; 53 | border: 1px solid #d4d4d4; 54 | border-top: none; 55 | } 56 | 57 | .luckysheet-cellFormat-protection{ 58 | position: relative; 59 | margin-top: 30px; 60 | margin-left: 40px; 61 | } 62 | 63 | .luckysheet-cellFormat-protection span{ 64 | font-size: 12px; 65 | color:#ff2929; 66 | padding-left: 12px; 67 | } -------------------------------------------------------------------------------- /src/controllers/listener.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Monitor special variables 3 | */ 4 | import {createProxy} from '../utils/util'; 5 | import Store from '../store/index'; 6 | import method from '../global/method'; 7 | import { getluckysheetfile } from '../methods/get' 8 | import { toJson } from '../global/api'; 9 | 10 | let undoTimer,redoTimer; 11 | function undoAccessible(len) { 12 | clearTimeout(undoTimer); 13 | undoTimer = setTimeout(() => { 14 | $('#luckysheet-icon-undo')[len ? 'removeClass' : 'addClass']('disabled'); 15 | }, 10); 16 | } 17 | function redoAccessible(len) { 18 | clearTimeout(redoTimer); 19 | redoTimer = setTimeout(() => { 20 | $('#luckysheet-icon-redo')[len ? 'removeClass' : 'addClass']('disabled'); 21 | }, 10); 22 | } 23 | 24 | const initListener = function(){ 25 | // createProxy(Store,['jfredo']); 26 | createProxy(Store, 'jfredo',(target, property, val, receiver)=>{ 27 | if (property !== 'length') { 28 | // 钩子函数 29 | method.createHookFunction('updated',val) 30 | } 31 | undoAccessible(Store.jfredo.length); 32 | } ); 33 | createProxy(Store, 'jfundo',(target, property, val, receiver)=>{ 34 | redoAccessible(Store.jfundo.length); 35 | } ); 36 | 37 | 38 | 39 | createProxy(Store, 'asyncLoad', (target, property, val, receiver)=>{ 40 | if(property === 'length' && val === 0){ 41 | method.createHookFunction('workbookCreateAfter', toJson()) 42 | } 43 | }) 44 | } 45 | 46 | export { 47 | initListener 48 | } -------------------------------------------------------------------------------- /.github/workflows/gitee-mirror.yml: -------------------------------------------------------------------------------- 1 | # 使用 GitHub Action 来解决手动同步到 Gitee 的问题 2 | # 效果:github repo 代码更新之后,会自动同步至 gitee 3 | # 使用到的 GitHub Action:https://github.com/Yikun/hub-mirror-action 4 | 5 | # This is a basic workflow to help you get started with Actions 6 | 7 | name: gitee-mirror 8 | 9 | # Controls when the action will run. Triggers the workflow on push or pull request 10 | # events but only for the master branch 11 | on: 12 | push: 13 | branches: 14 | - '*' 15 | 16 | jobs: 17 | # This workflow contains a single job called "build" 18 | build: 19 | # The type of runner that the job will run on 20 | runs-on: ubuntu-latest 21 | 22 | # Steps represent a sequence of tasks that will be executed as part of the job 23 | steps: 24 | - name: Mirror the Github organization repos to Gitee. 25 | uses: Yikun/hub-mirror-action@master 26 | with: 27 | src: github/mengshukeji 28 | dst: gitee/mengshukeji 29 | # 这里请填写与gitee上公钥匹配的的 ssh private key,参见:https://gitee.com/profile/sshkeys 30 | # 填写地址:https://github.com/ly525/luban-h5/settings/secrets 31 | dst_key: ${{ secrets.GITEE_PRIVATE_KEY }} 32 | # 这里请填写 gitee的令牌,参见:https://gitee.com/profile/personal_access_tokens 33 | # 填写地址:https://github.com/ly525/luban-h5/settings/secrets 34 | dst_token: ${{ secrets.GITEE_TOKEN }} 35 | # 项目同步白名单,可以选择填写多个,以英文逗号分割 36 | static_list: "Luckyexcel,Luckysheet,LuckysheetDemo,LuckyexcelDemo,LuckysheetDocs,chartMix,LuckysheetServer" 37 | # 是否强制同步 38 | force_update: true 39 | # 账号类型:对 luban-h5 而言是 user,因为是个人项目;如果是企业项目,请填写 org,因为是组织下的项目 40 | account_type: user 41 | -------------------------------------------------------------------------------- /docs/zh/about/company.md: -------------------------------------------------------------------------------- 1 | # 社区案例 2 | 3 | 我们搜集到了很多来自社区的案例反馈,还发现了以前我们没有预想到的Luckysheet使用场景。 4 | 5 | 积极聆听社区的声音,在大家的支持和反馈中不断更新迭代,已是我们的责任。 6 | 7 | 8 | ## 公司案例 9 | 10 | 11 | 12 | 13 | 19 | 25 | 31 | 37 | 38 | 39 | 41 | 43 | 45 | 47 | 48 | 49 |
14 | 15 | 16 | 17 |

雄安智评云数字科技有限公司

18 |
20 | 21 | 22 | 23 |

Code the Future

24 |
26 | 27 | 28 | 29 |

吉客云

30 |
32 | 33 | 34 | 35 |

华为

36 |
40 | 42 | 44 | 46 |
-------------------------------------------------------------------------------- /src/global/createsheet.js: -------------------------------------------------------------------------------- 1 | import { datagridgrowth } from './getdata'; 2 | import editor from './editor'; 3 | import rhchInit from './rhchInit'; 4 | import formula from './formula'; 5 | import { luckysheetrefreshgrid } from './refresh'; 6 | import sheetmanage from '../controllers/sheetmanage'; 7 | import Store from '../store'; 8 | 9 | export default function luckysheetcreatesheet(colwidth, rowheight, data, cfg, active) { 10 | if(active == null){ 11 | active = true; 12 | } 13 | 14 | Store.visibledatarow = []; 15 | Store.visibledatacolumn = []; 16 | Store.ch_width = 0; 17 | Store.rh_height = 0; 18 | Store.zoomRatio = 1; 19 | 20 | if(cfg != null){ 21 | Store.config = cfg; 22 | } 23 | else{ 24 | Store.config = {}; 25 | } 26 | 27 | if (data.length == 0) { 28 | Store.flowdata = datagridgrowth(data, rowheight, colwidth); 29 | } 30 | else if (data.length < rowheight && data[0].length < colwidth) { 31 | Store.flowdata = datagridgrowth(data, rowheight - data.length, colwidth - data[0].length); 32 | } 33 | else if (data.length < rowheight) { 34 | Store.flowdata = datagridgrowth(data, rowheight - data.length, 0); 35 | } 36 | else if (data[0].length < colwidth) { 37 | Store.flowdata = datagridgrowth(data, 0, colwidth - data[0].length); 38 | } 39 | else { 40 | Store.flowdata = data; 41 | } 42 | 43 | editor.webWorkerFlowDataCache(Store.flowdata);//worker存数据 44 | 45 | rhchInit(rowheight, colwidth); 46 | 47 | if(active){ 48 | sheetmanage.showSheet(); 49 | 50 | setTimeout(function () { 51 | sheetmanage.restoreCache(); 52 | formula.execFunctionGroup(); 53 | sheetmanage.restoreSheetAll(Store.currentSheetIndex); 54 | luckysheetrefreshgrid(); 55 | }, 1); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /docs/about/company.md: -------------------------------------------------------------------------------- 1 | # Community case 2 | 3 | We collected a lot of case feedback from the community, and also discovered Luckysheet usage scenarios that we did not expect before. 4 | 5 | It is our responsibility to actively listen to the voice of the community, and to continuously update and iterate with your support and feedback. 6 | 7 | 8 | ## Company Case 9 | 10 | 11 | 12 | 13 | 19 | 25 | 31 | 37 | 38 | 39 | 41 | 43 | 45 | 47 | 48 | 49 |
14 | 15 | 16 | 17 |

雄安智评云数字科技有限公司

18 |
20 | 21 | 22 | 23 |

Code the Future

24 |
26 | 27 | 28 | 29 |

吉客云

30 |
32 | 33 | 34 | 35 |

华为

36 |
40 | 42 | 44 | 46 |
-------------------------------------------------------------------------------- /src/controllers/sheetSearch.js: -------------------------------------------------------------------------------- 1 | function luckysheetbinary_search(arr, key) { 2 | let low = 0, high = arr.length - 1; 3 | 4 | while (low <= high) { 5 | let mid = parseInt((high + low) / 2); 6 | 7 | if (key < arr[mid] && (mid == 0 || key >= arr[mid - 1])) { 8 | return mid; 9 | } 10 | else if (key >= arr[mid]) { 11 | low = mid + 1; 12 | } 13 | else if (key < arr[mid]) { 14 | high = mid - 1; 15 | } 16 | else { 17 | return -1; 18 | } 19 | } 20 | } 21 | 22 | function luckysheetorder_search(arr, y) { 23 | let i = 0, 24 | row = 0, 25 | row_pre = 0, 26 | row_index = -1, 27 | i_ed = arr.length - 1; 28 | 29 | while (i < arr.length && i_ed >= 0 && i_ed >= i) { 30 | row = arr[i_ed]; 31 | 32 | if (i_ed == 0) { 33 | row_pre = 0; 34 | } 35 | else { 36 | row_pre = arr[i_ed - 1]; 37 | } 38 | 39 | if (y >= row_pre && y < row) { 40 | row_index = i_ed; 41 | break; 42 | } 43 | 44 | row = arr[i]; 45 | 46 | if (i == 0) { 47 | row_pre = 0; 48 | } 49 | else { 50 | row_pre = arr[i - 1]; 51 | } 52 | 53 | if (y >= row_pre && y < row) { 54 | row_index = i; 55 | break; 56 | } 57 | 58 | i++; 59 | i_ed--; 60 | } 61 | 62 | return row_index; 63 | } 64 | 65 | function luckysheet_searcharray(arr, y) { 66 | let index = arr.length - 1; 67 | 68 | if (arr.length < 40 || y <= arr[20] || y >= arr[index - 20]) { 69 | index = luckysheetorder_search(arr, y); 70 | } 71 | else { 72 | index = luckysheetbinary_search(arr, y); 73 | } 74 | 75 | return index; 76 | } 77 | 78 | export { 79 | luckysheet_searcharray, 80 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "luckysheet", 3 | "version": "2.1.13", 4 | "main": "dist/luckysheet.cjs.js", 5 | "module": "dist/luckysheet.esm.js", 6 | "browser": "dist/luckysheet.umd.js", 7 | "devDependencies": { 8 | "@babel/core": "^7.12.3", 9 | "@babel/preset-env": "^7.12.1", 10 | "@babel/runtime-corejs3": "^7.12.1", 11 | "@commitlint/cli": "^9.1.1", 12 | "@commitlint/config-conventional": "^9.1.1", 13 | "@rollup/plugin-babel": "^5.2.1", 14 | "@rollup/plugin-commonjs": "^13.0.0", 15 | "@rollup/plugin-node-resolve": "^8.0.1", 16 | "browser-sync": "^2.26.7", 17 | "commitizen": "^4.1.2", 18 | "cross-env": "^7.0.2", 19 | "delete": "^1.1.0", 20 | "gulp": "^4.0.2", 21 | "gulp-babel": "^8.0.0", 22 | "gulp-clean-css": "^4.3.0", 23 | "gulp-concat": "^2.6.1", 24 | "gulp-if": "^3.0.0", 25 | "gulp-uglify": "^3.0.2", 26 | "gulp-useref": "^4.0.1", 27 | "http-proxy-middleware": "^1.0.6", 28 | "rollup": "^2.32.1", 29 | "rollup-plugin-terser": "^6.1.0", 30 | "standard-version": "^8.0.2", 31 | "uuid": "^8.3.2", 32 | "vuepress": "^1.5.0", 33 | "vuepress-plugin-baidu-autopush": "^1.0.1", 34 | "vuepress-plugin-code-copy": "^1.0.6", 35 | "vuepress-plugin-seo": "^0.1.4", 36 | "vuepress-plugin-sitemap": "^2.3.1" 37 | }, 38 | "dependencies": { 39 | "@babel/runtime": "^7.12.1", 40 | "dayjs": "^1.9.6", 41 | "esbuild": "^0.11.6", 42 | "escape-html": "^1.0.3", 43 | "flatpickr": "^4.6.6", 44 | "jquery": "^2.2.4", 45 | "numeral": "^2.0.6", 46 | "pako": "^1.0.11" 47 | }, 48 | "scripts": { 49 | "build": "cross-env NODE_ENV=production gulp build", 50 | "dev": "cross-env NODE_ENV=development gulp dev", 51 | "docs:dev": "vuepress dev docs", 52 | "docs:build": "vuepress build docs", 53 | "commit": "git-cz", 54 | "release": "standard-version" 55 | }, 56 | "files": [ 57 | "dist" 58 | ], 59 | "config": { 60 | "commitizen": { 61 | "path": "./node_modules/cz-conventional-changelog" 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/global/datecontroll.js: -------------------------------------------------------------------------------- 1 | import { hasChinaword } from './validate'; 2 | import dayjs from 'dayjs' 3 | 4 | function isdatetime(s) { 5 | if (s == null || s.toString().length < 5) { 6 | return false; 7 | } 8 | else if(checkDateTime(s)){ 9 | return true; 10 | } 11 | else { 12 | return false; 13 | } 14 | 15 | function checkDateTime(str){ 16 | var reg1 = /^(\d{4})-(\d{1,2})-(\d{1,2})(\s(\d{1,2}):(\d{1,2})(:(\d{1,2}))?)?$/; 17 | var reg2 = /^(\d{4})\/(\d{1,2})\/(\d{1,2})(\s(\d{1,2}):(\d{1,2})(:(\d{1,2}))?)?$/; 18 | 19 | if(!reg1.test(str) && !reg2.test(str)){ 20 | return false; 21 | } 22 | 23 | var year = RegExp.$1, 24 | month = RegExp.$2, 25 | day = RegExp.$3; 26 | 27 | if(year < 1900){ 28 | return false; 29 | } 30 | 31 | if(month > 12){ 32 | return false; 33 | } 34 | 35 | if(day > 31){ 36 | return false; 37 | } 38 | 39 | if(month == 2){ 40 | if(new Date(year, 1, 29).getDate() == 29 && day > 29){ 41 | return false; 42 | } 43 | else if(new Date(year, 1, 29).getDate() != 29 && day > 28){ 44 | return false; 45 | } 46 | } 47 | 48 | return true; 49 | } 50 | } 51 | 52 | function diff(now, then) { 53 | return dayjs(now).diff(dayjs(then)); 54 | } 55 | 56 | function isdatatypemulti(s) { 57 | let type = {}; 58 | 59 | if (isdatetime(s)) { 60 | type["date"] = true; 61 | } 62 | 63 | if (!isNaN(parseFloat(s)) && !hasChinaword(s)) { 64 | type["num"] = true; 65 | } 66 | 67 | return type; 68 | } 69 | 70 | function isdatatype(s) { 71 | let type = "string"; 72 | 73 | if (isdatetime(s)) { 74 | type = "date"; 75 | } 76 | else if (!isNaN(parseFloat(s)) && !hasChinaword(s)) { 77 | type = "num"; 78 | } 79 | 80 | return type; 81 | } 82 | 83 | export { 84 | isdatetime, 85 | diff, 86 | isdatatypemulti, 87 | isdatatype, 88 | } 89 | -------------------------------------------------------------------------------- /src/css/iconCustom.css: -------------------------------------------------------------------------------- 1 | .luckysheet-icon-img-container.iconfont, 2 | .luckysheet-submenu-arrow .iconfont 3 | { 4 | font-size: 24px; 5 | } 6 | 7 | .luckysheet-toolbar-menu-button .luckysheet-iconfont-xiayige, 8 | .luckysheet-toolbar-combo-button .luckysheet-iconfont-xiayige 9 | { 10 | font-size: 12px; 11 | top: -8px; 12 | left: -3px; 13 | } 14 | 15 | .luckysheet-toolbar-select .luckysheet-iconfont-xiayige{ 16 | margin-right: 4px; 17 | } 18 | 19 | #luckysheet-icon-morebtn{ 20 | position: absolute; 21 | right: 15px; 22 | transform: translate(0,-50%); 23 | top: 50%; 24 | } 25 | 26 | 27 | .toolbar .luckysheet-icon-text-color, 28 | .toolbar .luckysheet-icon-cell-color, 29 | .toolbar .luckysheet-icon-border-all, 30 | .toolbar .luckysheet-icon-valign, 31 | .toolbar .luckysheet-icon-textwrap 32 | { 33 | margin-right: -3px; 34 | } 35 | 36 | .toolbar .luckysheet-icon-merge-button, 37 | .toolbar .luckysheet-icon-align, 38 | .toolbar .luckysheet-icon-rotation, 39 | .toolbar .luckysheet-icon-function, 40 | .toolbar .luckysheet-freezen-btn-horizontal 41 | { 42 | margin-right: -4px; 43 | } 44 | 45 | #luckysheet-icon-morebtn{ 46 | padding: 2px 13px 0 5px; 47 | } 48 | #luckysheet-icon-morebtn .iconfont{ 49 | top:-9px; 50 | } 51 | 52 | 53 | /* custom common style */ 54 | 55 | .lucky-button-custom{ 56 | cursor: pointer; 57 | display: flex; 58 | align-items: center; 59 | justify-content: center; 60 | } 61 | .lucky-button-custom:hover{ 62 | background-color: #E1E4E8; 63 | } 64 | 65 | /* more button border */ 66 | #luckysheet-icon-morebtn-div{ 67 | border: 1px solid rgb(212, 212, 212); 68 | } 69 | 70 | /* sheet bar add/menu button */ 71 | /* #luckysheet-sheets-add, #luckysheet-sheets-m{ 72 | padding: 1px 3px; 73 | } */ 74 | .luckysheet-sheets-add .iconfont, .luckysheet-sheets-m .iconfont{ 75 | font-size: 21px; 76 | } 77 | 78 | /* sheet bar left/right scroll */ 79 | #luckysheet-sheets-leftscroll , #luckysheet-sheets-rightscroll{ 80 | padding:6px 10px; 81 | } 82 | 83 | input.luckysheet-mousedown-cancel{ 84 | border:1px solid #A1A1A1; 85 | } 86 | input.luckysheet-mousedown-cancel:focus{ 87 | border: 1px solid rgb(1, 136, 251); 88 | outline: none; 89 | } -------------------------------------------------------------------------------- /docs/guide/resource.md: -------------------------------------------------------------------------------- 1 | # Tutorials and resources 2 | 3 | Open source software is inseparable from the contribution of the community. Here will be a list of tutorials, learning materials and supporting solutions provided by the community. 4 | 5 | If you have written or found an excellent tutorial and want to recommend it to us, please directly [edit this page](https://github.com/mengshukeji/Luckysheet/edit/master/docs/guide/resource.md) submit a PR. 6 | 7 | ## Blog 8 | - [How Luckysheet initializes the data with merged cells](https://www.cnblogs.com/DuShuSir/p/13272397.html)[Pending translation] 9 | - [How Luckysheet saves the data in the table to the database](https://www.cnblogs.com/DuShuSir/p/13857874.html)[Pending translation] 10 | - [Case of introducing Luckysheet into local HTML using CDN loading](https://www.cnblogs.com/DuShuSir/p/13859103.html)[Pending translation] 11 | - [Basic usage of Luckysheet, use `loadUrl` to load server data](https://blog.csdn.net/DCDC2020/article/details/108486525) 12 | - [Luckysheet import and export implementation-Java background processing](https://blog.csdn.net/u014632228/article/details/109738221) 13 | 14 | ## Front-end case 15 | 16 | ### Community Case 17 | - [luckysheet-vue-importAndExport](https://github.com/oy-paddy/luckysheet-vue-importAndExport/tree/master/) 18 | 19 | ## Back-end case 20 | 21 | ### Official case 22 | - [Java backend Luckysheet Server](https://github.com/mengshukeji/LuckysheetServer) 23 | 24 | ### Community Case 25 | - [Luckysheet save and restore](https://gitee.com/ichiva/luckysheet-saved-in-recovery) (Java version) 26 | - [Online form for collaborative editing based on Luckysheet](https://github.com/DilemmaVi/ecsheet) (Java version) 27 | - [Use .net core 3.1 and Npoi to make a basic export based on LuckSheet](https://gitee.com/xiong-kangli/luck-sheet_.-net-core) (.NET version) 28 | - [Collaborative editing in go language version](https://github.com/fandypeng/excel2config)(Go version) 29 | 30 | ## Learning Materials 31 | 32 | - [How to build a web data analysis report from 0 to 1](https://github.com/mengshukeji/LuckyResources/blob/master/ppt/%E5%A6%82%E4%BD%95%E4%BB%8E0%E5%88%B01%E6%90%AD%E5%BB%BA%20Web%20%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90%E6%8A%A5%E8%A1%A8.pptx)[Pending translation] -------------------------------------------------------------------------------- /src/global/count.js: -------------------------------------------------------------------------------- 1 | import Store from '../store'; 2 | import { getdatabyselectionNoCopy } from './getdata'; 3 | import { isRealNull, isRealNum } from './validate'; 4 | import { update } from './format'; 5 | import locale from '../locale/locale'; 6 | 7 | //表格计数栏 8 | export function countfunc() { 9 | if(Store.luckysheet_select_save.length == 0){ 10 | return; 11 | } 12 | 13 | let min = Infinity, //最小值 14 | max = -Infinity, //最大值 15 | sum = 0, //求和 16 | count = 0, //计数(非空单元格) 17 | mean = 0; //平均值 18 | 19 | for(let s = 0; s < Store.luckysheet_select_save.length; s++){ 20 | let data = getdatabyselectionNoCopy(Store.luckysheet_select_save[s]); 21 | 22 | for (let r = 0; r < data.length; r++) { 23 | for (let c = 0; c < data[0].length; c++) { 24 | if(isRealNull(data[r][c])){ 25 | continue; 26 | } 27 | 28 | count++; 29 | 30 | if(data[r][c].ct != null && data[r][c].ct.t == "d"){ 31 | continue; 32 | } 33 | 34 | let value = data[r][c].v; 35 | 36 | if(!isRealNum(value)){ 37 | continue; 38 | } 39 | 40 | value = parseFloat(value); 41 | 42 | sum += value; 43 | 44 | if(value < min){ 45 | min = value; 46 | } 47 | 48 | if(value > max){ 49 | max = value; 50 | } 51 | } 52 | } 53 | } 54 | 55 | let locale_formula = locale().formula; 56 | 57 | let ret = ""; 58 | ret += ""+locale_formula.count+":" + count + ""; 59 | 60 | //处理成亿万格式 61 | if (isFinite(max) || isFinite(min)) { 62 | ret += ""+locale_formula.sum+":" + update("w", sum) + ""; 63 | ret += ""+locale_formula.average+":" + update("w", Math.round(sum / count * 10000) / 10000) + ""; 64 | } 65 | 66 | if (isFinite(max)) { 67 | ret += ""+locale_formula.max+":" + update("w", max) + ""; 68 | } 69 | 70 | if (isFinite(min)) { 71 | ret += ""+locale_formula.min+":" + update("w", min) + ""; 72 | } 73 | 74 | $("#luckysheet-sta-content").html(ret); 75 | } 76 | -------------------------------------------------------------------------------- /src/global/array.js: -------------------------------------------------------------------------------- 1 | import { getcellvalue } from './getdata'; 2 | 3 | const luckysheetArray = { 4 | transpose: function (getdata, useGetcellValue=true) { 5 | let arr = []; 6 | if (getdata.length == 0) { 7 | return []; 8 | } 9 | 10 | if (getdata[0].length == 0) { 11 | return []; 12 | } 13 | 14 | for (let c = 0; c < getdata[0].length; c++) { 15 | let a = []; 16 | for (let r = 0; r < getdata.length; r++) { 17 | let value = ""; 18 | if (getdata[r] != null && getdata[r][c] != null) { 19 | if(useGetcellValue){ 20 | value = getcellvalue(r, c, getdata); 21 | } 22 | else{ 23 | value = getdata[r][c]; 24 | } 25 | } 26 | a.push(value); 27 | } 28 | arr.push(a); 29 | } 30 | 31 | return arr; 32 | }, 33 | minusClear: function(p, m){ 34 | if(m.row[0] > p.row[1] || m.row[1] < p.row[0] || m.column[0] > p.column[1] || m.column[1] < p.column[0]){ 35 | return null; 36 | } 37 | 38 | if(m.row[0] == p.row[0] && m.row[1] < p.row[1] && m.column[0] > p.column[0] && m.column[1] < p.column[1]){ 39 | return []; 40 | } 41 | 42 | let ret = [], range = {row:[], column:[]}; 43 | 44 | let row1 = null, column1 = [p.column[0], p.column[1]]; 45 | if(m.row[1] > p.row[0] && m.row[1] < p.row[1]){ 46 | row1 = [m.row[1] + 1, p.row[1]]; 47 | } 48 | else if(m.row[0] > p.row[0] && m.row[0] < p.row[1]){ 49 | row1 = [p.row[0], m.row[0] - 1]; 50 | } 51 | 52 | if(row1 != null){ 53 | ret.push({"row": row1, "column": column1}); 54 | } 55 | 56 | let row2 = [p.row[0], p.row[1]], column2 = null; 57 | if(m.column[1] > p.column[0] && m.column[1] < p.column[1]){ 58 | column2 = [m.column[1] + 1, p.column[1]]; 59 | } 60 | else if(m.column[0] > p.column[0] && m.column[0] < p.column[1]){ 61 | column2 = [p.column[0], m.column[0] - 1]; 62 | } 63 | 64 | if(column2 != null){ 65 | ret.push({"row": row2, "column": column2}); 66 | } 67 | 68 | return ret; 69 | } 70 | } 71 | 72 | export default luckysheetArray; -------------------------------------------------------------------------------- /src/global/location.js: -------------------------------------------------------------------------------- 1 | import { luckysheet_searcharray } from '../controllers/sheetSearch'; 2 | import Store from '../store'; 3 | 4 | function rowLocationByIndex(row_index) { 5 | let row = 0, row_pre = 0; 6 | row = Store.visibledatarow[row_index]; 7 | 8 | if (row_index == 0) { 9 | row_pre = 0; 10 | } 11 | else { 12 | row_pre = Store.visibledatarow[row_index - 1]; 13 | } 14 | 15 | return [row_pre, row, row_index]; 16 | } 17 | 18 | function rowLocation(y) { 19 | let row_index = luckysheet_searcharray(Store.visibledatarow, y); 20 | 21 | if (row_index == -1 && y > 0) { 22 | row_index = Store.visibledatarow.length - 1; 23 | } 24 | else if (row_index == -1 && y <= 0) { 25 | row_index = 0; 26 | } 27 | 28 | return rowLocationByIndex(row_index); 29 | } 30 | 31 | function colLocationByIndex(col_index){ 32 | let col = 0, col_pre = 0; 33 | col = Store.visibledatacolumn[col_index]; 34 | 35 | if (col_index == 0) { 36 | col_pre = 0; 37 | } 38 | else { 39 | col_pre = Store.visibledatacolumn[col_index - 1]; 40 | } 41 | 42 | return [col_pre, col, col_index]; 43 | } 44 | 45 | function colSpanLocationByIndex(col_index, span){ 46 | let col = 0, col_pre = 0; 47 | col = Store.visibledatacolumn[col_index + span - 1]; 48 | 49 | if (col_index == 0) { 50 | col_pre = 0; 51 | } 52 | else { 53 | col_pre = Store.visibledatacolumn[col_index - 1]; 54 | } 55 | 56 | return [col_pre, col, col_index]; 57 | } 58 | 59 | function colLocation(x) { 60 | let col_index = luckysheet_searcharray(Store.visibledatacolumn, x); 61 | 62 | if (col_index == -1 && x > 0) { 63 | col_index = Store.visibledatacolumn.length - 1; 64 | } 65 | else if (col_index == -1 && x <= 0) { 66 | col_index = 0; 67 | } 68 | 69 | return colLocationByIndex(col_index); 70 | } 71 | 72 | function mouseposition(x, y) { 73 | let container_offset = $("#" + Store.container).offset(); 74 | 75 | let newX = x - container_offset.left - Store.rowHeaderWidth, 76 | newY = y - container_offset.top - Store.infobarHeight - Store.toolbarHeight - Store.calculatebarHeight - Store.columnHeaderHeight; 77 | 78 | return [newX, newY]; 79 | } 80 | 81 | export { 82 | rowLocationByIndex, 83 | rowLocation, 84 | colLocationByIndex, 85 | colSpanLocationByIndex, 86 | colLocation, 87 | mouseposition, 88 | } 89 | -------------------------------------------------------------------------------- /deploy.bat: -------------------------------------------------------------------------------- 1 | # deploy Demo 2 | npm run build 3 | cd dist 4 | git init 5 | git remote add origin https://github.com/mengshukeji/LuckysheetDemo.git 6 | git config --local user.email "1414556676@qq.com" 7 | git config --local user.name "Dushusir" 8 | git add . 9 | git commit -m 'deploy Luckysheet demo' 10 | git push -f origin master:gh-pages 11 | 12 | # =============================================== 13 | 14 | # deploy Docs 15 | npm run docs:build 16 | cd docs/.vuepress/dist 17 | git init 18 | git remote add origin https://github.com/mengshukeji/LuckysheetDocs.git 19 | git add . 20 | git commit -m 'deploy Luckysheet docs' 21 | git push -f origin master:gh-pages 22 | 23 | # =============================================== 24 | 25 | # add a tags 26 | git tag -a doc -m "doc" 27 | 28 | 29 | # replease 30 | npm run build 31 | npm run release -- --release-as patch 32 | git push --follow-tags origin master 33 | npm publish 34 | 35 | # only publish 36 | npm run build 37 | git add . 38 | npm run commit 39 | npm version patch 40 | git push -u origin master 41 | npm publish 42 | 43 | 44 | # ============================================== 45 | 46 | # test feature branch 47 | git checkout -b fea origin/feature 48 | git pull 49 | 50 | ## After some test, create PR merge feature to master branch 51 | 52 | git checkout master 53 | git branch -d fea 54 | 55 | # =============================================== 56 | 57 | # test pull request: https://docs.github.com/cn/free-pro-team@latest/github/collaborating-with-issues-and-pull-requests/checking-out-pull-requests-locally 58 | 59 | # 139 is ID, dev is branch name 60 | git fetch origin pull/139/head:test-139 61 | git checkout test-139 62 | # test code 63 | git push origin test-139 64 | # create new PR ,merge test-139 to master 65 | 66 | # list all remote and local branchs 67 | git branch -a 68 | # delete remote branch 69 | git push origin --delete dev 70 | git checkout master 71 | # delete local branch 72 | git branch -d dev 73 | 74 | # pr 75 | ## 1. fork 到自己的仓库 76 | 77 | ## 2. git clone 到本地 78 | 79 | ## 3. 上游建立连接 80 | git remote add upstream https://github.com/mengshukeji/Luckysheet.git 81 | 82 | ## 4. 创建开发分支 83 | git checkout -b dev 84 | 85 | ## 5. 修改提交代码 86 | git add . 87 | git commit -m "add" 88 | git push origin dev 89 | 90 | ## 6. 同步代码,将最新代码同步到本地 91 | git fetch upstream 92 | git rebase upstream/master 93 | 94 | ## 7. 如果有冲突(没有可以略过) 95 | git status # 查看冲突文件,并修改冲突 96 | git add . 97 | git rebase --continue 98 | 99 | ## 8.提交分支代码 100 | git push origin dev 101 | 102 | ## 7. 提交pr 103 | 去自己github仓库对应fork的项目下new pull request -------------------------------------------------------------------------------- /src/expendPlugins/chart/chartmix.css: -------------------------------------------------------------------------------- 1 | .luckysheet-datavisual-quick-menu{width:120px;overflow:auto;margin-top:15px}.luckysheet-datavisual-quick-menu::-webkit-scrollbar{display:none}.luckysheet-datavisual-quick-menu>div{text-align:left;padding:4px 4px;border-right:3px solid #fff;color:#777;cursor:pointer;line-height:1.4em;word-wrap:break-word}.luckysheet-datavisual-quick-menu>div:hover{color:#000}.luckysheet-datavisual-quick-menu>div i{width:15px}.luckysheet-datavisual-quick-menu>div:hover i{color:#ff7e7e}.luckysheet-datavisual-quick-menu>div.luckysheet-datavisual-quick-menu-active{border-right:3px solid #ff7e7e;color:#000;font-weight:700}.luckysheet-datavisual-quick-menu>div.luckysheet-datavisual-quick-menu-active:hover i{color:#000}.luckysheet-datavisual-quick-range{padding:5px 0}.luckysheet-datavisual-range-container{background:#fff;border:1px solid #d9d9d9;border-top:1px solid silver;min-width:20px;width:100%;max-width:200px;display:inline-block}.luckysheet-datavisual-range-container-focus{border:1px solid #4d90fe;box-shadow:inset 0 1px 2px rgba(0,0,0,.3);outline:none}.luckysheet-datavisual-range-input,.luckysheet-datavisual-range-input:focus{background:transparent!important;border:none!important;box-sizing:border-box;box-shadow:none;height:25px;margin:0;outline:none!important;padding:1px 8px!important;width:100%}.luckysheet-datavisual-range-button-container{overflow:hidden;padding:0 0 0 8px;text-align:right;width:21px}.luckysheet-datavisual-range-button-container div{padding:2px 10px 0 10px;font-size:18px;cursor:pointer;color:#6598f3}.luckysheet-datavisual-range-button-container div:hover{color:#ff7e7e}.luckysheet-datavisual-quick-m{margin-top:5px;min-height:500px;top:50px;font-size:12px}.luckysheet-datavisual-quick-list{left:110px;right:0;bottom:0;top:80px;position:absolute;overflow:auto;border-top:1px solid #e5e5e5;padding:5px 3px 35px 3px}.luckysheet-datavisual-quick-list-title{padding:4px 6px;background:#e5e5e5;margin-top:10px}.luckysheet-datavisual-quick-list-ul{overflow:hidden}.luckysheet-datavisual-quick-list-item{display:inline-block;margin:5px 8px;border:1px solid #dadada;width:100px;height:80px}.luckysheet-datavisual-quick-list-item:hover{border:1px solid #ff7e7e;box-shadow:0 0 20px #ff7e7e}.luckysheet-datavisual-quick-list-item img{display:inline-block;width:100px;height:80px}.luckysheet-datavisual-quick-list-item-active{border:1px solid #6598f3;box-shadow:0 0 20px #6598f3}.chart-base-slider .el-slider__runway.show-input{margin-right:72px}.chart-base-slider .el-slider__input.el-input-number--mini{width:56px}.chart-base-slider .input_content{margin:6px 0 0 5px}.title{font-weight:700}.el-row{font-size:12px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.chartSetting{width:100%;height:100%} -------------------------------------------------------------------------------- /docs/zh/about/sponsor.md: -------------------------------------------------------------------------------- 1 | # 赞助 2 | 3 | ## 为什么赞助 4 | 5 | 如果您经营一家企业并在盈利产品中使用Luckysheet,那么赞助Luckysheet开发将具有商业意义:它可确保您的产品所依赖的项目保持健康并得到积极维护。 它还可以帮助您在Luckysheet社区中曝光,使得更多的人关注您的产品。 6 | 7 | 当然,如果Luckysheet在您的工作或个人项目中为您提供了帮助,也欢迎请作者喝杯果汁😋。 8 | 9 | ## 赞助方式 10 | 11 | Luckysheet是MIT许可的开源项目,其持续稳定的开发离不开这些优秀的 [**支持者**](#赞助者列表)。 如果您想加入他们,请考虑: 12 | 13 | - [成为Patreon的支持者或赞助商](https://www.patreon.com/mengshukeji) 14 | - [成为Open Collective的支持者或赞助商](https://opencollective.com/luckysheet) 15 | - 通过PayPal,微信或支付宝一次性捐赠 16 | 17 | | PayPal | 微信 | 支付宝 | 18 | |---|---|---| 19 | | [Paypal Me](https://www.paypal.me/wbfsa) | | | 20 | 21 | ### Patreon和OpenCollective有什么区别? 22 | 23 | 通过Patreon捐赠的资金将直接用于支持menshshukeji在Luckysheet上的工作。 通过OpenCollective捐赠的资金由透明费用管理,将用于补偿核心团队成员的工作和费用或赞助社区活动。 通过在任一平台上捐款,您的姓名/徽标将得到适当的认可和曝光。 24 | 25 | ## 赞助者列表 26 | 27 | (按时间顺序排列) 28 | - *勇 ¥ 30 29 | - 虚我 ¥ 200 30 | - 甜党 ¥ 50 31 | - Alphabet(Google)-gcf ¥ 1 32 | - **平 ¥ 100 33 | - **东 ¥ 10 34 | - debugger ¥ 20 35 | - 烦了烦 ¥ 10 36 | - 文顶顶 ¥ 200 37 | - yangxshn ¥ 10 38 | - 爱乐 ¥ 100 39 | - 小李飞刀刀 ¥ 66 40 | - 张铭 ¥ 200 41 | - 曹治军 ¥ 1 42 | - *特 ¥ 10 43 | - **权 ¥ 9.9 44 | - **sdmq ¥ 20 45 | - *旭 ¥ 10 46 | - Quentin ¥ 20 47 | - 周宇凡 ¥ 100 48 | - *超 ¥ 10 49 | - 维宁 ¥ 100 50 | - hyy ¥ 20 51 | - 雨亭寒江月 ¥ 50 52 | - **功 ¥ 10 53 | - **光 ¥ 20 54 | - terrywan ¥ 100 55 | - 王晓洪 ¥ 10 56 | - Sun ¥ 10 57 | - 忧绣 ¥ 100 58 | - Jasonx ¥ 10 59 | - 国勇 ¥ 66.6 60 | - 郎志 ¥ 100 61 | - 匿名 ¥ 1 62 | - ni ¥ 100 63 | - 苏 ¥ 50 64 | - Mads_chan ¥ 1 65 | - LK ¥ 100 66 | - 智连方舟 李汪石 ¥ 168 67 | - **发 ¥ 260 68 | - *超 ¥ 10 69 | - *勇 ¥ 10 70 | - *腾 ¥ 15 71 | - 名字好难起 ¥ 20 72 | - 大山 ¥ 1 73 | - waiting ¥ 1000 74 | - **宇 ¥ 10.00 75 | - 刘小帅的哥哥 ¥ 20.00 76 | - 宁静致远 ¥ 10.00 77 | - Eleven ¥ 1.00 78 | - **帆 ¥ 188 79 | - henry ¥ 100 80 | - .波罗 ¥ 50 81 | - 花落有家 ¥ 50 82 | - 踏遍南水北山 ¥ 1 83 | - LC ¥ 5 84 | - **明 ¥ 8.80 85 | - *军 ¥ 20 86 | - 张彪 ¥ 50 87 | - 企业文档云@肖敏 ¥ 10 88 | - 匿名 ¥ 50 89 | - 逍遥行 ¥ 10 90 | - z.wasaki ¥ 50 91 | - Make Children ¥ 20 92 | - Foam ¥ 20 93 | - 奥特曼( o|o)ノ三 ¥ 50 94 | - **凯 ¥ 10 95 | - **兵 ¥ 20 96 | - **川 ¥ 1 97 | - 二万 ¥ 50 98 | - 蔚然成林 ¥ 10 99 | - 邹杰 ¥ 10 100 | - 张永强 ¥ 50 101 | - 鱼得水 ¥ 50 102 | - Ccther ¥ 1 103 | - Eric Cheng ¥ 10 104 | - 佚名 ¥ 1 105 | - 花叶 ¥ 50 106 | - GT ¥ 20 107 | - 菜菜心 ¥ 10 108 | - fisher ¥ 1 109 | - JC ¥ 5 110 | - 佚名 ¥ 20 111 | - 独孤一剑 ¥ 50 112 | - mxt ¥ 20 113 | - 一叶迷山 ¥ 100 114 | - Jeff ¥ 100 115 | - 八千多条狗🐶 ¥ 100 116 | - 晓峰 ¥ 10 117 | - 戒 ¥ 1 118 | - 浪里个浪 ¥ 1 119 | - 回调函数 ¥ 50 120 | - 赖瓜子 ¥ 5 121 | - Milo•J ¥ 20 122 | - 可道云 ¥ 200 123 | - *程 ¥ 10 124 | - 来一杯卡布酸奶 ¥ 5 -------------------------------------------------------------------------------- /src/plugins/js/jquery.mousewheel.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery Mousewheel 3.1.13 3 | * 4 | * Copyright 2015 jQuery Foundation and other contributors 5 | * Released under the MIT license. 6 | * http://jquery.org/license 7 | */ 8 | !function(a){"function"==typeof define&&define.amd?define(["jquery"],a):"object"==typeof exports?module.exports=a:a(jQuery)}(function(a){function b(b){var g=b||window.event,h=i.call(arguments,1),j=0,l=0,m=0,n=0,o=0,p=0;if(b=a.event.fix(g),b.type="mousewheel","detail"in g&&(m=-1*g.detail),"wheelDelta"in g&&(m=g.wheelDelta),"wheelDeltaY"in g&&(m=g.wheelDeltaY),"wheelDeltaX"in g&&(l=-1*g.wheelDeltaX),"axis"in g&&g.axis===g.HORIZONTAL_AXIS&&(l=-1*m,m=0),j=0===m?l:m,"deltaY"in g&&(m=-1*g.deltaY,j=m),"deltaX"in g&&(l=g.deltaX,0===m&&(j=-1*l)),0!==m||0!==l){if(1===g.deltaMode){var q=a.data(this,"mousewheel-line-height");j*=q,m*=q,l*=q}else if(2===g.deltaMode){var r=a.data(this,"mousewheel-page-height");j*=r,m*=r,l*=r}if(n=Math.max(Math.abs(m),Math.abs(l)),(!f||f>n)&&(f=n,d(g,n)&&(f/=40)),d(g,n)&&(j/=40,l/=40,m/=40),j=Math[j>=1?"floor":"ceil"](j/f),l=Math[l>=1?"floor":"ceil"](l/f),m=Math[m>=1?"floor":"ceil"](m/f),k.settings.normalizeOffset&&this.getBoundingClientRect){var s=this.getBoundingClientRect();o=b.clientX-s.left,p=b.clientY-s.top}return b.deltaX=l,b.deltaY=m,b.deltaFactor=f,b.offsetX=o,b.offsetY=p,b.deltaMode=0,h.unshift(b,j,l,m),e&&clearTimeout(e),e=setTimeout(c,200),(a.event.dispatch||a.event.handle).apply(this,h)}}function c(){f=null}function d(a,b){return k.settings.adjustOldDeltas&&"mousewheel"===a.type&&b%120===0}var e,f,g=["wheel","mousewheel","DOMMouseScroll","MozMousePixelScroll"],h="onwheel"in document||document.documentMode>=9?["wheel"]:["mousewheel","DomMouseScroll","MozMousePixelScroll"],i=Array.prototype.slice;if(a.event.fixHooks)for(var j=g.length;j;)a.event.fixHooks[g[--j]]=a.event.mouseHooks;var k=a.event.special.mousewheel={version:"3.1.12",setup:function(){if(this.addEventListener)for(var c=h.length;c;)this.addEventListener(h[--c],b,!1);else this.onmousewheel=b;a.data(this,"mousewheel-line-height",k.getLineHeight(this)),a.data(this,"mousewheel-page-height",k.getPageHeight(this))},teardown:function(){if(this.removeEventListener)for(var c=h.length;c;)this.removeEventListener(h[--c],b,!1);else this.onmousewheel=null;a.removeData(this,"mousewheel-line-height"),a.removeData(this,"mousewheel-page-height")},getLineHeight:function(b){var c=a(b),d=c["offsetParent"in a.fn?"offsetParent":"parent"]();return d.length||(d=a("body")),parseInt(d.css("fontSize"),10)||parseInt(c.css("fontSize"),10)||16},getPageHeight:function(b){return a(b).height()},settings:{adjustOldDeltas:!0,normalizeOffset:!0}};a.fn.extend({mousewheel:function(a){return a?this.bind("mousewheel",a):this.trigger("mousewheel")},unmousewheel:function(a){return this.unbind("mousewheel",a)}})}); -------------------------------------------------------------------------------- /src/plugins/jquery.sPage.css: -------------------------------------------------------------------------------- 1 | .spage-total { 2 | display: inline-block; 3 | margin-right: 10px; 4 | line-height: 29px; 5 | color: #666; 6 | font-size: 14px 7 | } 8 | 9 | .spage-number { 10 | display: inline-block; 11 | color: #666; 12 | font-size: 14px 13 | } 14 | .selectNum { 15 | font-size: 14px; 16 | height: 27px; 17 | box-sizing: border-box; 18 | vertical-align: top; 19 | line-height: 27px; 20 | border: 1px solid #ddd; 21 | margin-left: 5px; 22 | vertical-align: middle; 23 | } 24 | .spage-number button { 25 | position: relative; 26 | box-sizing: border-box; 27 | display: inline-block; 28 | margin-left: -1px; 29 | padding: 0 10px; 30 | line-height: 27px; 31 | border: 1px solid #ddd; 32 | text-align: center; 33 | transition: all .2s; 34 | cursor: pointer; 35 | outline: none; 36 | background: 0 0; 37 | user-select: none; 38 | color: #333; 39 | background: #fff; 40 | vertical-align: middle; 41 | } 42 | .prevBtn, .nextBtn { 43 | width: 16px; 44 | height: 27px; 45 | background: url(images/js.png) no-repeat center center; 46 | background-size: 100% auto; 47 | display: block; 48 | transform: rotate(180deg); 49 | } 50 | .nextBtn { 51 | transform: rotate(0); 52 | } 53 | .spage-number button.active { 54 | background: #2d98e6; 55 | color: #fff; 56 | border-color: #2d98e6; 57 | z-index: 3 58 | } 59 | 60 | .spage-number button.active:hover { 61 | background: #2d98e6; 62 | color: #fff; 63 | border-color: #2d98e6; 64 | z-index: 3 65 | } 66 | 67 | .spage-number button:hover { 68 | background-color: #eee 69 | } 70 | 71 | .spage-number button.button-disabled { 72 | cursor: not-allowed; 73 | color: #ccc 74 | } 75 | 76 | .spage-number .spage-after, 77 | .spage-before { 78 | padding: 0; 79 | width: 40px 80 | } 81 | 82 | .spage-skip { 83 | display: inline-block; 84 | margin-left: 5px; 85 | line-height: 27px; 86 | color: #666; 87 | font-size: 14px 88 | } 89 | 90 | .spage-skip input { 91 | box-sizing: border-box; 92 | display: inline-block; 93 | width: 45px; 94 | height: 29px; 95 | text-align: center; 96 | vertical-align: top; 97 | border: 1px solid #ddd; 98 | background: 0 0; 99 | outline: none; 100 | transition: all .2s 101 | } 102 | 103 | .spage-skip input:focus { 104 | border-color: #2d98e6 105 | } 106 | 107 | .spage-skip button { 108 | display: inline-block; 109 | padding: 0 14px; 110 | line-height: 27px; 111 | vertical-align: top; 112 | color: #333; 113 | border: 1px solid #ddd; 114 | cursor: pointer; 115 | transition: all .2s; 116 | outline: none; 117 | background: 0 0; 118 | user-select: none; 119 | background-color: #fff; 120 | } 121 | 122 | .spage-skip button:hover { 123 | background: #2d98e6; 124 | color: #fff; 125 | border: 1px solid #2d98e6 126 | } 127 | 128 | 129 | -------------------------------------------------------------------------------- /src/utils/chartUtil.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 生成随机图表id 3 | */ 4 | function generateRandomKey(prefix) { 5 | if (prefix == null) { 6 | prefix = 'chart' 7 | } 8 | 9 | var userAgent = window.navigator.userAgent 10 | .replace(/[^a-zA-Z0-9]/g, '') 11 | .split('') 12 | var mid = '' 13 | for (var i = 0; i < 12; i++) { 14 | mid += userAgent[Math.round(Math.random() * (userAgent.length - 1))] 15 | } 16 | var time = new Date().getTime() 17 | 18 | return prefix + '_' + mid + '_' + time 19 | } 20 | /** 21 | * 深度克隆数据,包括对象,数组,map 22 | * @param {*} obj 对象,数组,map 23 | */ 24 | function deepCopy(obj) { 25 | if (!isObject(obj) && !isMap(obj)) { 26 | return obj; 27 | } 28 | 29 | let cloneObj; 30 | if (isMap(obj)) { 31 | cloneObj = new Map(); 32 | for (let key of obj.keys()) { 33 | let value = obj.get(key); 34 | if (isMap(value) || isObject(value) || Array.isArray(obj)) { 35 | let copyVal = deepCopy(value); 36 | cloneObj.set(key, copyVal); 37 | } else { 38 | cloneObj.set(key, value); 39 | } 40 | } 41 | } else if (typeof obj === "function") { 42 | cloneObj = obj 43 | } else { 44 | cloneObj = Array.isArray(obj) ? [] : {}; 45 | if (obj instanceof HTMLElement) { 46 | cloneObj = obj.cloneNode(true) 47 | } else { 48 | for (let key in obj) { 49 | // if (obj.hasOwnProperty(key)) { 50 | if (Object.prototype.hasOwnProperty.call(obj, key)) { 51 | cloneObj[key] = 52 | isMap(obj[key]) || isObject(obj[key]) 53 | ? deepCopy(obj[key]) 54 | : obj[key]; 55 | } 56 | } 57 | 58 | } 59 | } 60 | return cloneObj; 61 | } 62 | 63 | /** 64 | * 判断参数是否是Object类型 65 | * @param {*} o 66 | */ 67 | function isObject(o) { 68 | return ( 69 | !isMap(o) && 70 | (typeof o === 'object' || typeof o === 'function') && 71 | o !== null 72 | ); 73 | } 74 | 75 | /** 76 | * 判断参数是否是Map类型 77 | * @param {*} obj 78 | */ 79 | function isMap(obj) { 80 | if (obj instanceof Map) { 81 | return true; 82 | } else { 83 | return false; 84 | } 85 | } 86 | 87 | // 替换temp中的${xxx}为指定内容 ,temp:字符串,这里指html代码,dataarry:一个对象{"xxx":"替换的内容"} 88 | // 例:luckysheet.replaceHtml("${image}",{"image":"abc","jskdjslf":"abc"}) ==> abc 89 | function replaceHtml(temp, dataarry) { 90 | return temp.replace(/\$\{([\w]+)\}/g, function (s1, s2) { var s = dataarry[s2]; if (typeof (s) != "undefined") { return s; } else { return s1; } }); 91 | } 92 | 93 | function hasChinaword(s) { 94 | var patrn = /[\u4E00-\u9FA5]|[\uFE30-\uFFA0]/gi; 95 | if (!patrn.exec(s)) { 96 | return false; 97 | } 98 | else { 99 | return true; 100 | } 101 | } 102 | 103 | export { 104 | isMap, 105 | isObject, 106 | deepCopy, 107 | generateRandomKey, 108 | replaceHtml 109 | } -------------------------------------------------------------------------------- /src/methods/get.js: -------------------------------------------------------------------------------- 1 | import { chatatABC } from '../utils/util'; 2 | import Store from '../store'; 3 | 4 | function getSheetIndex(index) { 5 | for (let i = 0; i < Store.luckysheetfile.length; i++) { 6 | if (Store.luckysheetfile[i]["index"] == index) { 7 | return i; 8 | } 9 | } 10 | 11 | return null; 12 | } 13 | 14 | function getRangetxt(sheetIndex, range, currentIndex) { 15 | let sheettxt = ""; 16 | 17 | if (currentIndex == null) { 18 | currentIndex = Store.currentSheetIndex; 19 | } 20 | 21 | if (sheetIndex != currentIndex) { 22 | //sheet名字包含'的,引用时应该替换为'' 23 | sheettxt = Store.luckysheetfile[getSheetIndex(sheetIndex)].name.replace(/'/g,"''"); 24 | //如果包含除a-z、A-Z、0-9、下划线等以外的字符那么就用单引号包起来 25 | if(/^[:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD][:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]*$/.test(sheettxt)) 26 | { 27 | sheettxt = sheettxt+"!"; 28 | } 29 | else 30 | { 31 | sheettxt="'"+sheettxt+"'!"; 32 | } 33 | } 34 | 35 | let row0 = range["row"][0], row1 = range["row"][1]; 36 | let column0 = range["column"][0], column1 = range["column"][1]; 37 | 38 | if (row0 == null && row1 == null) { 39 | return sheettxt + chatatABC(column0) + ":" + chatatABC(column1); 40 | } 41 | else if (column0 == null && column1 == null) { 42 | return sheettxt + (row0 + 1) + ":" + (row1 + 1); 43 | } 44 | else { 45 | if (column0 == column1 && row0 == row1) { 46 | return sheettxt + chatatABC(column0) + (row0 + 1); 47 | } 48 | else { 49 | return sheettxt + chatatABC(column0) + (row0 + 1) + ":" + chatatABC(column1) + (row1 + 1); 50 | } 51 | } 52 | } 53 | 54 | function getluckysheet_select_save() { 55 | return Store.luckysheet_select_save; 56 | } 57 | 58 | function getluckysheet_scroll_status() { 59 | return Store.luckysheet_scroll_status; 60 | } 61 | 62 | function getluckysheetfile(plugin) { 63 | // 获取图表数据 64 | if(plugin){ 65 | Store.luckysheetfile.forEach(file => { 66 | if(!!file.chart){ 67 | file.chart.forEach((chartObj)=>{ 68 | const chartJson = Store.getChartJson(chartObj.chart_id); 69 | chartObj.chartOptions = chartJson; 70 | }) 71 | } 72 | }); 73 | } 74 | 75 | return Store.luckysheetfile; 76 | } 77 | 78 | function getconfig() { 79 | return Store.config; 80 | } 81 | 82 | function getvisibledatarow() { 83 | return Store.visibledatarow; 84 | } 85 | 86 | function getvisibledatacolumn() { 87 | return Store.visibledatacolumn; 88 | } 89 | 90 | export { 91 | getSheetIndex, 92 | getRangetxt, 93 | getluckysheet_select_save, 94 | getluckysheet_scroll_status, 95 | getluckysheetfile, 96 | getconfig, 97 | getvisibledatarow, 98 | getvisibledatacolumn, 99 | } 100 | -------------------------------------------------------------------------------- /docs/about/sponsor.md: -------------------------------------------------------------------------------- 1 | # Sponsor 2 | 3 | ## Why sponsor 4 | 5 | If you run a business and is using Luckysheet in a revenue-generating product, it would make business sense to sponsor Luckysheet development: it ensures the project that your product relies on stays healthy and actively maintained. It can also help your exposure in the Luckysheet community and more people pay attention to your products. 6 | 7 | Of course, individual users are also welcome to buy author a glass of juice if Luckysheet has helped you in your work or personal projects 😋. 8 | 9 | ## How to sponsor 10 | 11 | Luckysheet is an MIT-licensed open source project with its ongoing development made possible entirely by the support of these awesome [**backers**](#sponsors-list). If you'd like to join them, please consider: 12 | 13 | - [Become a backer or sponsor on Patreon](https://www.patreon.com/mengshukeji). 14 | - [Become a backer or sponsor on Open Collective](https://opencollective.com/luckysheet). 15 | - One-time donation via PayPal, WeChat or Alipay 16 | 17 | | PayPal | WeChat | Alipay | 18 | |---|---|---| 19 | | [Paypal Me](https://www.paypal.me/wbfsa) | | | 20 | 21 | ### What's the difference between Patreon and OpenCollective? 22 | 23 | Funds donated via Patreon go directly to support mengshukeji's work on Luckysheet. Funds donated via OpenCollective are managed with transparent expenses and will be used for compensating work and expenses for core team members or sponsoring community events. Your name/logo will receive proper recognition and exposure by donating on either platform. 24 | 25 | ## Sponsors List 26 | 27 | (Sort by time) 28 | - *勇 ¥ 30 29 | - 虚我 ¥ 200 30 | - 甜党 ¥ 50 31 | - Alphabet(Google)-gcf ¥ 1 32 | - **平 ¥ 100 33 | - **东 ¥ 10 34 | - debugger ¥ 20 35 | - 烦了烦 ¥ 10 36 | - 文顶顶 ¥ 200 37 | - yangxshn ¥ 10 38 | - 爱乐 ¥ 100 39 | - 小李飞刀刀 ¥ 66 40 | - 张铭 ¥ 200 41 | - 曹治军 ¥ 1 42 | - *特 ¥ 10 43 | - **权 ¥ 9.9 44 | - **sdmq ¥ 20 45 | - *旭 ¥ 10 46 | - Quentin ¥ 20 47 | - 周宇凡 ¥ 100 48 | - *超 ¥ 10 49 | - 维宁 ¥ 100 50 | - hyy ¥ 20 51 | - 雨亭寒江月 ¥ 50 52 | - **功 ¥ 10 53 | - **光 ¥ 20 54 | - terrywan ¥ 100 55 | - 王晓洪 ¥ 10 56 | - Sun ¥ 10 57 | - 忧绣 ¥ 100 58 | - Jasonx ¥ 10 59 | - 国勇 ¥ 66.6 60 | - 郎志 ¥ 100 61 | - 匿名 ¥ 1 62 | - ni ¥ 100 63 | - 苏 ¥ 50 64 | - Mads_chan ¥ 1 65 | - LK ¥ 100 66 | - 智连方舟 李汪石 ¥ 168 67 | - **发 ¥ 260 68 | - *超 ¥ 10 69 | - *勇 ¥ 10 70 | - *腾 ¥ 15 71 | - 名字好难起 ¥ 20 72 | - 大山 ¥ 1 73 | - waiting ¥ 1000 74 | - **宇 ¥ 10.00 75 | - 刘小帅的哥哥 ¥ 20.00 76 | - 宁静致远 ¥ 10.00 77 | - Eleven ¥ 1.00 78 | - **帆 ¥ 188 79 | - henry ¥ 100 80 | - .波罗 ¥ 50 81 | - 花落有家 ¥ 50 82 | - 踏遍南水北山 ¥ 1 83 | - LC ¥ 5 84 | - **明 ¥ 8.80 85 | - *军 ¥ 20 86 | - 张彪 ¥ 50 87 | - 企业文档云@肖敏 ¥ 10 88 | - 匿名 ¥ 50 89 | - 逍遥行 ¥ 10 90 | - z.wasaki ¥ 50 91 | - Make Children ¥ 20 92 | - Foam ¥ 20 93 | - 奥特曼( o|o)ノ三 ¥ 50 94 | - **凯 ¥ 10 95 | - **兵 ¥ 20 96 | - **川 ¥ 1 97 | - 二万 ¥ 50 98 | - 蔚然成林 ¥ 10 99 | - 邹杰 ¥ 10 100 | - 张永强 ¥ 50 101 | - 鱼得水 ¥ 50 102 | - Ccther ¥ 1 103 | - Eric Cheng ¥ 10 104 | - 佚名 ¥ 1 105 | - 花叶 ¥ 50 106 | - GT ¥ 20 107 | - 菜菜心 ¥ 10 108 | - fisher ¥ 1 109 | - JC ¥ 5 110 | - 佚名 ¥ 20 111 | - 独孤一剑 ¥ 50 112 | - mxt ¥ 20 113 | - 一叶迷山 ¥ 100 114 | - Jeff ¥ 100 115 | - 八千多条狗🐶 ¥ 100 116 | - 晓峰 ¥ 10 117 | - 戒 ¥ 1 118 | - 浪里个浪 ¥ 1 119 | - 回调函数 ¥ 50 120 | - 赖瓜子 ¥ 5 121 | - Milo•J ¥ 20 122 | - 可道云 ¥ 200 123 | - *程 ¥ 10 124 | - 来一杯卡布酸奶 ¥ 5 -------------------------------------------------------------------------------- /src/demoData/sheetPivotTable.js: -------------------------------------------------------------------------------- 1 | window.sheetPivotTable = { 2 | "name": "PivotTable", 3 | "color": "", 4 | "config": {}, 5 | "index": "7", 6 | "chart": [], 7 | "status": 0, 8 | "order": "7", 9 | "column": 18, 10 | "row": 36, 11 | "celldata": [{ 12 | "r": 0, 13 | "c": 0, 14 | "v": "count:score" 15 | }, { 16 | "r": 0, 17 | "c": 1, 18 | "v": "science" 19 | }, { 20 | "r": 0, 21 | "c": 2, 22 | "v": "mathematics" 23 | }, { 24 | "r": 0, 25 | "c": 3, 26 | "v": "foreign language" 27 | }, { 28 | "r": 0, 29 | "c": 4, 30 | "v": "English" 31 | }, { 32 | "r": 0, 33 | "c": 5, 34 | "v": "total" 35 | }, { 36 | "r": 1, 37 | "c": 0, 38 | "v": "Alex" 39 | }, { 40 | "r": 1, 41 | "c": 1, 42 | "v": 1 43 | }, { 44 | "r": 1, 45 | "c": 2, 46 | "v": 1 47 | }, { 48 | "r": 1, 49 | "c": 3, 50 | "v": 1 51 | }, { 52 | "r": 1, 53 | "c": 4, 54 | "v": 1 55 | }, { 56 | "r": 1, 57 | "c": 5, 58 | "v": 4 59 | }, { 60 | "r": 2, 61 | "c": 0, 62 | "v": "Joy" 63 | }, { 64 | "r": 2, 65 | "c": 1, 66 | "v": 1 67 | }, { 68 | "r": 2, 69 | "c": 2, 70 | "v": 1 71 | }, { 72 | "r": 2, 73 | "c": 3, 74 | "v": 1 75 | }, { 76 | "r": 2, 77 | "c": 4, 78 | "v": 1 79 | }, { 80 | "r": 2, 81 | "c": 5, 82 | "v": 4 83 | }, { 84 | "r": 3, 85 | "c": 0, 86 | "v": "Tim" 87 | }, { 88 | "r": 3, 89 | "c": 1, 90 | "v": 1 91 | }, { 92 | "r": 3, 93 | "c": 2, 94 | "v": 1 95 | }, { 96 | "r": 3, 97 | "c": 3, 98 | "v": 1 99 | }, { 100 | "r": 3, 101 | "c": 4, 102 | "v": 1 103 | }, { 104 | "r": 3, 105 | "c": 5, 106 | "v": 4 107 | }, { 108 | "r": 4, 109 | "c": 0, 110 | "v": "total" 111 | }, { 112 | "r": 4, 113 | "c": 1, 114 | "v": 3 115 | }, { 116 | "r": 4, 117 | "c": 2, 118 | "v": 3 119 | }, { 120 | "r": 4, 121 | "c": 3, 122 | "v": 3 123 | }, { 124 | "r": 4, 125 | "c": 4, 126 | "v": 3 127 | }, { 128 | "r": 4, 129 | "c": 5, 130 | "v": 12 131 | }], 132 | "ch_width": 4748, 133 | "rh_height": 1790, 134 | "luckysheet_select_save": [{ 135 | "row": [0, 0], 136 | "column": [0, 0] 137 | }], 138 | "luckysheet_selection_range": [], 139 | "scrollLeft": 0, 140 | "scrollTop": 0, 141 | "isPivotTable": true, 142 | "pivotTable": { 143 | "pivot_select_save": { 144 | "left": 0, 145 | "width": 73, 146 | "top": 0, 147 | "height": 19, 148 | "left_move": 0, 149 | "width_move": 369, 150 | "top_move": 0, 151 | "height_move": 259, 152 | "row": [0, 12], 153 | "column": [0, 4], 154 | "row_focus": 0, 155 | "column_focus": 0 156 | }, 157 | "pivotDataSheetIndex": 6, //The sheet index where the source data is located 158 | "column": [{ 159 | "index": 3, 160 | "name": "subject", 161 | "fullname": "subject" 162 | }], 163 | "row": [{ 164 | "index": 1, 165 | "name": "student", 166 | "fullname": "student" 167 | }], 168 | "filter": [], 169 | "values": [{ 170 | "index": 4, 171 | "name": "score", 172 | "fullname": "count:score", 173 | "sumtype": "COUNTA", 174 | "nameindex": 0 175 | }], 176 | "showType": "column", 177 | "pivotDatas": [ 178 | ["count:score", "science", "mathematics", "foreign language", "English", "total"], 179 | ["Alex", 1, 1, 1, 1, 4], 180 | ["Joy", 1, 1, 1, 1, 4], 181 | ["Tim", 1, 1, 1, 1, 4], 182 | ["total", 3, 3, 3, 3, 12] 183 | ], 184 | "drawPivotTable": false, 185 | "pivotTableBoundary": [5, 6] 186 | } 187 | } 188 | 189 | // export default sheetPivotTable; -------------------------------------------------------------------------------- /src/controllers/print.js: -------------------------------------------------------------------------------- 1 | import luckysheetConfigsetting from './luckysheetConfigsetting'; 2 | import {zoomChange} from './zoom'; 3 | import sheetmanage from './sheetmanage'; 4 | import server from './server'; 5 | import {rowLocationByIndex, colLocationByIndex,mouseposition,rowLocation,colLocation} from '../global/location'; 6 | import Store from '../store'; 7 | 8 | let ExcelPlaceholder = { 9 | "[tabName]":"&A", 10 | "[CurrentDate]":"&D", 11 | "[fileName]":"&F", 12 | "[background]":"&G", 13 | "[Shadow]":"&H", 14 | "[TotalPages]":"&N", 15 | "[pageNumber]":"&P", 16 | "[CurrentTime]":"&T", 17 | "[filePath]":"&Z", 18 | } 19 | 20 | // Get the pixel value per millimeter 21 | function getOneMmsPx (){ 22 | let div = document.createElement("div"); 23 | div.style.width = "1mm"; 24 | document.querySelector("body").appendChild(div); 25 | let mm1 = div.getBoundingClientRect(); 26 | let w = mm1.width; 27 | $(div).remove(); 28 | return mm1.width; 29 | } 30 | 31 | export function viewChange(curType, preType){ 32 | let currentSheet = sheetmanage.getSheetByIndex(); 33 | 34 | if(currentSheet.config==null){ 35 | currentSheet.config = {}; 36 | } 37 | 38 | if(currentSheet.config.sheetViewZoom==null){ 39 | currentSheet.config.sheetViewZoom = {}; 40 | } 41 | 42 | let defaultZoom = 1, type="zoomScaleNormal"; 43 | printLineAndNumberDelete(currentSheet); 44 | if(curType=="viewNormal"){ 45 | type = "viewNormalZoomScale"; 46 | } 47 | else if(curType=="viewLayout"){ 48 | type = "viewLayoutZoomScale"; 49 | } 50 | else if(curType=="viewPage"){ 51 | type = "viewPageZoomScale"; 52 | defaultZoom = 0.6; 53 | printLineAndNumberCreate(currentSheet); 54 | } 55 | 56 | 57 | 58 | let curZoom = currentSheet.config.sheetViewZoom[type]; 59 | if(curZoom==null){ 60 | curZoom = defaultZoom; 61 | } 62 | 63 | currentSheet.config.curentsheetView = curType; 64 | 65 | if (Store.clearjfundo) { 66 | Store.jfredo.push({ 67 | "type": "viewChange", 68 | "curType": curType, 69 | "preType": preType, 70 | "sheetIndex": Store.currentSheetIndex, 71 | }); 72 | } 73 | 74 | // Store.zoomRatio = curZoom; 75 | // server.saveParam("all", Store.currentSheetIndex, curZoom, { "k": "zoomRatio" }); 76 | server.saveParam("cg", Store.currentSheetIndex, curType, { "k": "curentsheetView" }); 77 | 78 | Store.currentSheetView = curType; 79 | 80 | zoomChange(curZoom); 81 | } 82 | 83 | 84 | function printLineAndNumberDelete(sheet){ 85 | 86 | } 87 | 88 | function printLineAndNumberCreate(sheet){ 89 | 90 | } 91 | 92 | function switchViewBtn($t){ 93 | let $viewList = $t.parent(), preType=$viewList.find("luckysheet-print-viewBtn-active").attr("type"); 94 | if($t.attr("type") == preType){ 95 | return; 96 | } 97 | 98 | let curType = $t.attr("type"); 99 | if(curType!=null){ 100 | viewChange(curType, preType); 101 | } 102 | else{ 103 | return; 104 | } 105 | 106 | $t.parent().find(".luckysheet-print-viewBtn").removeClass("luckysheet-print-viewBtn-active"); 107 | $t.addClass("luckysheet-print-viewBtn-active"); 108 | } 109 | 110 | export function printInitial(){ 111 | let container = luckysheetConfigsetting.container; 112 | let _this = this; 113 | $("#"+container).find(".luckysheet-print-viewBtn").click(function(){ 114 | switchViewBtn($(this)); 115 | }); 116 | 117 | } 118 | -------------------------------------------------------------------------------- /src/config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * The default luckysheet config object. 3 | */ 4 | export default { 5 | container: "luckysheet", //容器的ID 6 | loading:{}, //自定义loading 7 | column: 60, //空表格默认的列数量 8 | row: 84, //空表格默认的行数据量 9 | allowCopy: true, //是否允许拷贝 10 | showtoolbar: true, //是否第二列显示工具栏 11 | showinfobar: true, //是否显示顶部名称栏 12 | showsheetbar: true, //是否显示底部表格名称区域 13 | showstatisticBar: true, //是否显示底部计数栏 14 | pointEdit: false, //是否是编辑器插入表格模式 15 | pointEditUpdate: null, //编辑器表格更新函数 16 | pointEditZoom: 1, //编辑器表格编辑时缩放比例 17 | // menu: "undo|redo|freezenrow|freezencolumn|download|share|chart|pivot", 18 | data: [{ "name": "Sheet1", color: "", "status": "1", "order": "0", "data": [], "config": {}, "index":0 }, { "name": "Sheet2", color: "", "status": "0", "order": "1", "data": [], "config": {}, "index":1 }, { "name": "Sheet3", color: "", "status": "0", "order": "2", "data": [], "config": {}, "index":2 }], //客户端sheet数据[sheet1, sheet2, sheet3] 19 | title: "Luckysheet Demo", //表格的名称 20 | userInfo:false,// 右上角的用户信息展示样式,支持 1. boolean类型:false:不展示,ture:展示默认 ' rabbit' ,2. HTML模板字符串或者普通字符串,如:' Lucky'或者'用户名', 3. 对象格式,设置 userImage:用户头像地址 和 userName:用户名 4. 不设置或者设置undefined同设置false 21 | userMenuItem: [{url:"www.baidu.com", "icon":'', "name":"我的表格"}, {url:"www.baidu.com", "icon":'', "name":"退出登陆"}], //点击右上角的用户信息弹出的菜单 22 | myFolderUrl: "www.baidu.com", //左上角<返回按钮的链接 23 | config: {}, //表格行高、列宽、合并单元格、公式等设置 24 | fullscreenmode: true, //是否全屏模式,非全屏模式下,标记框不会强制选中。 25 | devicePixelRatio: window.devicePixelRatio, //设备比例,比例越大表格分标率越高 26 | allowEdit: true, //是否允许前台编辑 27 | loadUrl: "", // 配置loadUrl的地址,luckysheet会通过ajax请求表格数据,默认载入status为1的sheet数据中的所有data,其余的sheet载入除data字段外的所有字段 28 | loadSheetUrl: "", //配置loadSheetUrl的地址,参数为gridKey(表格主键) 和 index(sheet主键合集,格式为[1,2,3]),返回的数据为sheet的data字段数据集合 29 | gridKey: "", // 表格唯一标识符 30 | updateUrl: "", //表格数据的更新地址 31 | updateImageUrl: "", //缩略图的更新地址 32 | allowUpdate: false, //是否允许编辑后的后台更新 33 | functionButton: "", //右上角功能按钮,例如' ' 34 | showConfigWindowResize: true, //图表和数据透视表的配置会在右侧弹出,设置弹出后表格是否会自动缩进 35 | enableAddRow: true,//允许添加行 36 | enableAddBackTop: true,//允许回到顶部 37 | // enablePage: false,//允许加载下一页 38 | autoFormatw: false, //自动格式化超过4位数的数字为 亿万格式 例:true or "true" or "TRUE" 39 | accuracy: undefined, //设置传输来的数值的精确位数,小数点后n位 传参数为数字或数字字符串,例: "0" 或 0 40 | pageInfo:{ 41 | 'queryExps':'', 42 | 'reportId':'', 43 | 'fields':'', 44 | 'mobile':'', 45 | 'frezon':'', 46 | 'currentPage':'', 47 | "totalPage":10, 48 | "pageUrl":"", 49 | }, 50 | editMode: false, //是否为编辑模式 51 | beforeCreateDom: null,//表格创建之前的方法 52 | fireMousedown: null, //单元格数据下钻 53 | lang: 'en', //language 54 | plugins: [], //plugins, e.g. ['chart'] 55 | forceCalculation:false,//强制刷新公式,公式较多会有性能问题,慎用 56 | rowHeaderWidth: 46, 57 | columnHeaderHeight: 20, 58 | defaultColWidth:73, 59 | defaultRowHeight:19, 60 | defaultFontSize:10, 61 | limitSheetNameLength:true, //是否限制工作表名的长度 62 | defaultSheetNameMaxLength:31, //默认工作表名称的最大长度 63 | sheetFormulaBar:true, //是否显示公式栏 64 | showtoolbarConfig:{}, //自定义工具栏 65 | showsheetbarConfig:{}, //自定义底部sheet页 66 | showstatisticBarConfig:{}, //自定义计数栏 67 | cellRightClickConfig:{}, //自定义单元格右键菜单 68 | sheetRightClickConfig:{}, //自定义底部sheet页右击菜单 69 | imageUpdateMethodConfig:{}, //自定义图片同步方式 70 | } 71 | -------------------------------------------------------------------------------- /src/global/rhchInit.js: -------------------------------------------------------------------------------- 1 | import Store from '../store'; 2 | import { computeRowlenByContent,computeColWidthByContent } from './getRowlen'; 3 | import luckysheetConfigsetting from '../controllers/luckysheetConfigsetting'; 4 | 5 | export default function rhchInit(rowheight, colwidth) { 6 | zoomSetting();//Zoom sheet on first load 7 | //行高 8 | if(rowheight != null){ 9 | Store.visibledatarow = []; 10 | Store.rh_height = 0; 11 | 12 | for (let r = 0; r < rowheight; r++) { 13 | let rowlen = Store.defaultrowlen; 14 | 15 | if (Store.config["rowlen"] != null && Store.config["rowlen"][r] != null) { 16 | rowlen = Store.config["rowlen"][r]; 17 | } 18 | 19 | if (Store.config["rowhidden"] != null && Store.config["rowhidden"][r] != null) { 20 | Store.visibledatarow.push(Store.rh_height); 21 | continue; 22 | } 23 | 24 | // 自动行高计算 25 | if (rowlen === 'auto') { 26 | rowlen = computeRowlenByContent(Store.flowdata, r); 27 | } 28 | Store.rh_height += Math.round((rowlen + 1) * Store.zoomRatio); 29 | 30 | Store.visibledatarow.push(Store.rh_height); //行的临时长度分布 31 | } 32 | 33 | // 如果增加行和回到顶部按钮隐藏,则减少底部空白区域,但是预留足够空间给单元格下拉按钮 34 | if (!luckysheetConfigsetting.enableAddRow && !luckysheetConfigsetting.enableAddBackTop) { 35 | Store.rh_height += 29; 36 | } else { 37 | Store.rh_height += 80; //最底部增加空白 38 | } 39 | 40 | } 41 | 42 | //列宽 43 | if(colwidth != null){ 44 | Store.visibledatacolumn = []; 45 | Store.ch_width = 0; 46 | 47 | let maxColumnlen = 120; 48 | 49 | for (let c = 0; c < colwidth; c++) { 50 | let firstcolumnlen = Store.defaultcollen; 51 | 52 | if (Store.config["columnlen"] != null && Store.config["columnlen"][c] != null) { 53 | firstcolumnlen = Store.config["columnlen"][c]; 54 | } 55 | else { 56 | if (Store.flowdata[0] != null && Store.flowdata[0][c] != null) { 57 | if (firstcolumnlen > 300) { 58 | firstcolumnlen = 300; 59 | } 60 | else if (firstcolumnlen < Store.defaultcollen) { 61 | firstcolumnlen = Store.defaultcollen; 62 | } 63 | 64 | if (firstcolumnlen != Store.defaultcollen) { 65 | if (Store.config["columnlen"] == null) { 66 | Store.config["columnlen"] = {}; 67 | } 68 | 69 | Store.config["columnlen"][c] = firstcolumnlen; 70 | } 71 | } 72 | } 73 | 74 | if(Store.config["colhidden"] != null && Store.config["colhidden"][c] != null){ 75 | Store.visibledatacolumn.push(Store.ch_width); 76 | continue; 77 | } 78 | 79 | // 自动行高计算 80 | if (firstcolumnlen === 'auto') { 81 | firstcolumnlen = computeColWidthByContent(Store.flowdata, c, rowheight); 82 | } 83 | Store.ch_width += Math.round((firstcolumnlen + 1)*Store.zoomRatio); 84 | 85 | Store.visibledatacolumn.push(Store.ch_width);//列的临时长度分布 86 | 87 | // if(maxColumnlen < firstcolumnlen + 1){ 88 | // maxColumnlen = firstcolumnlen + 1; 89 | // } 90 | } 91 | 92 | // Store.ch_width += 120; 93 | Store.ch_width += maxColumnlen; 94 | } 95 | } 96 | 97 | 98 | export function zoomSetting(){ 99 | //zoom 100 | Store.rowHeaderWidth = luckysheetConfigsetting.rowHeaderWidth * Store.zoomRatio; 101 | Store.columnHeaderHeight = luckysheetConfigsetting.columnHeaderHeight *Store.zoomRatio; 102 | $("#luckysheet-rows-h").width((Store.rowHeaderWidth-1.5)); 103 | $("#luckysheet-cols-h-c").height((Store.columnHeaderHeight-1.5)); 104 | $("#luckysheet-left-top").css({width:Store.rowHeaderWidth-1.5, height:Store.columnHeaderHeight-1.5}); 105 | } 106 | -------------------------------------------------------------------------------- /src/controllers/cellDatePickerCtrl.js: -------------------------------------------------------------------------------- 1 | import menuButton from './menuButton'; 2 | import formula from '../global/formula'; 3 | import Store from '../store'; 4 | import flatpickr from 'flatpickr' 5 | import dayjs from "dayjs"; 6 | import { update, datenum_local } from '../global/format'; 7 | import { setCellValue, setCellFormat } from '../global/api'; 8 | 9 | const fitFormat = (formatStr) => { 10 | let dateFormat = formatStr.replace(/y/g, 'Y'); 11 | dateFormat = dateFormat.replace(/d/g, 'D'); 12 | dateFormat = dateFormat.replace(/h/g, 'H'); 13 | 14 | dateFormat = dateFormat.replace(/上午\/下午/g, 'A'); 15 | dateFormat = dateFormat.replace(/上午/g, 'A'); 16 | dateFormat = dateFormat.replace(/下午/g, 'A'); 17 | 18 | dateFormat = dateFormat.replace(/AM\/PM/g, 'A'); 19 | dateFormat = dateFormat.replace(/AM/g, 'A'); 20 | dateFormat = dateFormat.replace(/PM/g, 'A'); 21 | dateFormat = dateFormat.replace(/\"/g, ''); 22 | 23 | if (dateFormat.includes('A')) { 24 | dateFormat = dateFormat.replace(/H/g, 'h'); 25 | } 26 | return dateFormat 27 | } 28 | 29 | const cellDatePickerCtrl = { 30 | cellFocus: function (r, c, cell) { 31 | let row = Store.visibledatarow[r], 32 | row_pre = r == 0 ? 0 : Store.visibledatarow[r - 1]; 33 | let col = Store.visibledatacolumn[c], 34 | col_pre = c == 0 ? 0 : Store.visibledatacolumn[c - 1]; 35 | 36 | let margeset = menuButton.mergeborer(Store.flowdata, r, c); 37 | let type = cell.ct.fa || 'YYYY-MM-DD'; 38 | let defaultDate = update('yyyy-MM-dd hh:mm:ss', cell.v); 39 | let dateFormat = fitFormat(type); 40 | let enableTime = false; 41 | let noCalendar = false; 42 | let enableSeconds = false; 43 | let time_24hr = true; 44 | let hasChineseTime = false; 45 | 46 | 47 | if (!!margeset) { 48 | row = margeset.row[1]; 49 | row_pre = margeset.row[0]; 50 | 51 | col = margeset.column[1]; 52 | col_pre = margeset.column[0]; 53 | } 54 | 55 | $(".cell-date-picker").show().css({ 56 | width: col - col_pre + 1, 57 | height: row - row_pre + 1, 58 | left: col_pre, 59 | top: row_pre 60 | }) 61 | 62 | if (/[上午下午]/.test(type)) { 63 | hasChineseTime = true 64 | } 65 | if (/[Hhms]/.test(dateFormat)) { 66 | enableTime = true; 67 | } 68 | if (!/[YMD]/.test(dateFormat)) { 69 | noCalendar = true; 70 | } 71 | if (/s/.test(dateFormat)) { 72 | enableSeconds = true; 73 | } 74 | if (/A/.test(dateFormat)) { 75 | time_24hr = false; 76 | } 77 | 78 | const fp = flatpickr('#luckysheet-input-box', { 79 | allowInput: false, 80 | noCalendar, 81 | enableSeconds, 82 | enableTime, 83 | dateFormat, 84 | time_24hr, 85 | defaultDate, 86 | onClose() { 87 | setTimeout(() => { 88 | fp.destroy() 89 | }, 0); 90 | }, 91 | parseDate: (datestr, format) => { 92 | return dayjs(datestr).toDate(); 93 | }, 94 | formatDate: (date, format, locale) => { 95 | if (hasChineseTime) { 96 | return dayjs(date).format(format).replace('AM', '上午').replace('PM', '下午') 97 | } 98 | return dayjs(date).format(format); 99 | }, 100 | onChange: function (selectedDates, dateStr) { 101 | let currentVal = datenum_local(new Date(selectedDates)) 102 | $("#luckysheet-rich-text-editor").html(dateStr); 103 | setCellValue(r, c, currentVal, { isRefresh: false }) 104 | setCellFormat(r, c, 'ct', cell.ct) 105 | if (!enableTime) { 106 | formula.updatecell(Store.luckysheetCellUpdate[0], Store.luckysheetCellUpdate[1]); 107 | } 108 | } 109 | }); 110 | 111 | $("#luckysheet-input-box").click(); 112 | }, 113 | } 114 | 115 | export default cellDatePickerCtrl; 116 | -------------------------------------------------------------------------------- /docs/.vuepress/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | base: '/LuckysheetDocs/', 3 | locales: { 4 | // 键名是该语言所属的子路径 5 | // 作为特例,默认语言可以使用 '/' 作为其路径。 6 | '/': { 7 | lang: 'en-US', // 将会被设置为 的 lang 属性 8 | title: 'Luckysheet Document', 9 | description: 'Luckysheet is an online spreadsheet like excel that is powerful, simple to configure, and completely open source.This site contains official configuration document, API, and tutorial.' 10 | }, 11 | '/zh/': { 12 | lang: 'zh-CN', 13 | title: 'Luckysheet文档', 14 | description: 'Luckysheet ,一款纯前端类似excel的在线表格,功能强大、配置简单、完全开源。本站包含官方配置文档,API,教程。' 15 | }, 16 | 17 | }, 18 | themeConfig: { 19 | domain: 'https://mengshukeji.github.io/LuckysheetDemo', 20 | logo: '/img/logo.png', 21 | author: 'Luckysheet', 22 | // 仓库地址 23 | repo: 'mengshukeji/Luckysheet', 24 | // 允许编辑链接文字 25 | editLinks: true, 26 | // 仓库的文档目录 27 | docsDir: 'docs', 28 | // 页面滚动 29 | smoothScroll: true, 30 | locales: { 31 | '/': { 32 | selectText: 'Languages', 33 | label: 'English', 34 | ariaLabel: 'Select language', 35 | editLinkText: 'Edit this page on GitHub', 36 | lastUpdated: 'Last Updated', 37 | serviceWorker: { 38 | updatePopup: { 39 | message: "New content is available.", 40 | buttonText: "Refresh" 41 | } 42 | }, 43 | nav: [ 44 | { text: 'Home', link: '/' }, 45 | { text: 'Guide', link: '/guide/' }, 46 | { text: 'Demo', link: 'https://mengshukeji.github.io/LuckysheetDemo/' }, 47 | { 48 | text: 'More', 49 | ariaLabel: 'More', 50 | items: [ 51 | { text: 'About', link: '/about/' } 52 | ] 53 | }, 54 | ], 55 | // 侧边栏 56 | sidebar: { 57 | '/guide/': [ 58 | '', 59 | 'config', 60 | 'sheet', 61 | 'cell', 62 | 'operate', 63 | 'api', 64 | 'resource', 65 | 'FAQ', 66 | 'contribute' 67 | ], 68 | '/about/': [ 69 | '', 70 | 'sponsor', 71 | 'company' 72 | ], 73 | }, 74 | }, 75 | '/zh/': { 76 | // 多语言下拉菜单的标题 77 | selectText: '选择语言', 78 | // 该语言在下拉菜单中的标签 79 | label: '简体中文', 80 | ariaLabel: '选择语言', 81 | // 编辑链接文字 82 | editLinkText: '在 GitHub 上编辑此页', 83 | lastUpdated: '上次更新', 84 | // Service Worker 的配置 85 | serviceWorker: { 86 | updatePopup: { 87 | message: "发现新内容可用.", 88 | buttonText: "刷新" 89 | } 90 | }, 91 | // 导航栏 92 | nav: [ 93 | { text: '首页', link: '/zh/' }, 94 | { text: '指南', link: '/zh/guide/' }, 95 | { text: '演示', link: 'https://mengshukeji.github.io/LuckysheetDemo/' }, 96 | { 97 | text: '了解更多', 98 | ariaLabel: '了解更多', 99 | items: [ 100 | { text: '关于', link: '/zh/about/' } 101 | ] 102 | }, 103 | ], 104 | // 侧边栏 105 | sidebar: { 106 | '/zh/guide/': [ 107 | '', 108 | 'config', 109 | 'sheet', 110 | 'cell', 111 | 'operate', 112 | 'api', 113 | 'resource', 114 | 'FAQ', 115 | 'contribute' 116 | ], 117 | '/zh/about/': [ 118 | '', 119 | 'sponsor', 120 | 'company' 121 | ], 122 | }, 123 | }, 124 | 125 | }, 126 | }, 127 | plugins: { 128 | 'vuepress-plugin-baidu-autopush': {}, 129 | 'sitemap': { 130 | hostname: 'https://mengshukeji.github.io/LuckysheetDocs' 131 | }, 132 | 'vuepress-plugin-code-copy': true, 133 | 'seo': { 134 | siteTitle: (_, $site) => $site.title, 135 | title: $page => $page.title, 136 | description: $page => $page.frontmatter.description, 137 | author: (_, $site) => $site.themeConfig.author, 138 | tags: $page => $page.frontmatter.tags, 139 | twitterCard: _ => 'summary_large_image', 140 | type: $page => ['guide'].some(folder => $page.regularPath.startsWith('/' + folder)) ? 'article' : 'website', 141 | url: (_, $site, path) => ($site.themeConfig.domain || '') + path, 142 | image: ($page, $site) => $page.frontmatter.image && (($site.themeConfig.domain && !$page.frontmatter.image.startsWith('http') || '') + $page.frontmatter.image), 143 | publishedAt: $page => $page.frontmatter.date && new Date($page.frontmatter.date), 144 | modifiedAt: $page => $page.lastUpdated && new Date($page.lastUpdated), 145 | } 146 | } 147 | } -------------------------------------------------------------------------------- /src/global/scroll.js: -------------------------------------------------------------------------------- 1 | import luckysheetFreezen from '../controllers/freezen'; 2 | import { luckysheet_searcharray } from '../controllers/sheetSearch'; 3 | import { luckysheetrefreshgrid } from '../global/refresh'; 4 | import Store from '../store'; 5 | import method from '../global/method' 6 | 7 | let scrollRequestAnimationFrameIni = true,scrollRequestAnimationFrame = false, scrollTimeOutCancel=null; 8 | 9 | function execScroll(){ 10 | let scrollLeft = $("#luckysheet-scrollbar-x").scrollLeft(), 11 | scrollTop = $("#luckysheet-scrollbar-y").scrollTop(); 12 | luckysheetrefreshgrid(scrollLeft, scrollTop); 13 | scrollRequestAnimationFrame = window.requestAnimationFrame(execScroll); 14 | } 15 | 16 | //全局滚动事件 17 | export default function luckysheetscrollevent(isadjust) { 18 | let $t = $("#luckysheet-cell-main"); 19 | let scrollLeft = $("#luckysheet-scrollbar-x").scrollLeft(), 20 | scrollTop = $("#luckysheet-scrollbar-y").scrollTop(), 21 | canvasHeight = $("#luckysheetTableContent").height(); // canvas高度 22 | 23 | // clearTimeout(scrollTimeOutCancel); 24 | 25 | // scrollTimeOutCancel = setTimeout(() => { 26 | // scrollRequestAnimationFrameIni = true; 27 | // window.cancelAnimationFrame(scrollRequestAnimationFrame); 28 | // }, 500); 29 | 30 | // if (!!isadjust) { 31 | // let scrollHeight = $t.get(0).scrollHeight; 32 | // let windowHeight = $t.height(); 33 | // let scrollWidth = $t.get(0).scrollWidth; 34 | // let windowWidth = $t.width(); 35 | 36 | // let maxScrollLeft = scrollWidth - windowWidth; 37 | // let maxScrollTop = scrollHeight - windowHeight; 38 | 39 | // let visibledatacolumn_c = Store.visibledatacolumn, visibledatarow_c = Store.visibledatarow; 40 | 41 | // if (luckysheetFreezen.freezenhorizontaldata != null) { 42 | // visibledatarow_c = luckysheetFreezen.freezenhorizontaldata[3]; 43 | // } 44 | 45 | // if (luckysheetFreezen.freezenverticaldata != null) { 46 | // visibledatacolumn_c = luckysheetFreezen.freezenverticaldata[3]; 47 | // } 48 | 49 | // let col_ed = luckysheet_searcharray(visibledatacolumn_c, scrollLeft); 50 | // let row_ed = luckysheet_searcharray(visibledatarow_c, scrollTop); 51 | 52 | // let refreshLeft = scrollLeft , refreshTop = scrollTop; 53 | 54 | // if (col_ed <= 0) { 55 | // scrollLeft = 0; 56 | // } 57 | // else { 58 | // scrollLeft = visibledatacolumn_c[col_ed - 1]; 59 | // } 60 | 61 | // if (row_ed <= 0) { 62 | // scrollTop = 0; 63 | // } 64 | // else { 65 | // scrollTop = visibledatarow_c[row_ed - 1]; 66 | // } 67 | // } 68 | 69 | if (luckysheetFreezen.freezenhorizontaldata != null) { 70 | if (scrollTop < luckysheetFreezen.freezenhorizontaldata[2]) { 71 | scrollTop = luckysheetFreezen.freezenhorizontaldata[2]; 72 | $("#luckysheet-scrollbar-y").scrollTop(scrollTop); 73 | return; 74 | } 75 | } 76 | 77 | if (luckysheetFreezen.freezenverticaldata != null) { 78 | if (scrollLeft < luckysheetFreezen.freezenverticaldata[2]) { 79 | scrollLeft = luckysheetFreezen.freezenverticaldata[2]; 80 | $("#luckysheet-scrollbar-x").scrollLeft(scrollLeft); 81 | return; 82 | } 83 | } 84 | 85 | $("#luckysheet-cols-h-c").scrollLeft(scrollLeft);//列标题 86 | $("#luckysheet-rows-h").scrollTop(scrollTop);//行标题 87 | 88 | $t.scrollLeft(scrollLeft).scrollTop(scrollTop); 89 | 90 | $("#luckysheet-input-box-index").css({ 91 | "left": $("#luckysheet-input-box").css("left"), 92 | "top": (parseInt($("#luckysheet-input-box").css("top")) - 20) + "px", 93 | "z-index": $("#luckysheet-input-box").css("z-index") 94 | }).show(); 95 | 96 | // if(scrollRequestAnimationFrameIni && Store.scrollRefreshSwitch){ 97 | // execScroll(); 98 | // scrollRequestAnimationFrameIni = false; 99 | // } 100 | 101 | luckysheetrefreshgrid(scrollLeft, scrollTop); 102 | 103 | 104 | $("#luckysheet-bottom-controll-row").css("left", scrollLeft); 105 | 106 | //有选区且有冻结时,滚动适应 107 | if(luckysheetFreezen.freezenhorizontaldata != null || luckysheetFreezen.freezenverticaldata != null){ 108 | luckysheetFreezen.scrollAdapt(); 109 | } 110 | 111 | if(!method.createHookFunction("scroll", {scrollLeft, scrollTop, canvasHeight})){ return; } 112 | 113 | } -------------------------------------------------------------------------------- /src/utils/math.js: -------------------------------------------------------------------------------- 1 | import { isRealNum } from '../global/validate'; 2 | 3 | /* 4 | * 判断obj是否为一个整数 5 | */ 6 | function isInteger(obj) { 7 | return Math.floor(obj) === obj; 8 | } 9 | 10 | /* 11 | * 将一个浮点数转成整数,返回整数和倍数。如 3.14 >> 314,倍数是 100 12 | * @param floatNum {number} 小数 13 | * @return {object} 14 | * {times:100, num: 314} 15 | */ 16 | function toInteger(floatNum) { 17 | var ret = { times: 1, num: 0 }; 18 | 19 | if (isInteger(floatNum)) { 20 | ret.num = floatNum; 21 | return ret; 22 | } 23 | 24 | var strfi = floatNum + ''; 25 | var dotPos = strfi.indexOf('.'); 26 | var len = strfi.substr(dotPos + 1).length; 27 | var times = Math.pow(10, len); 28 | var intNum = parseInt(floatNum * times + 0.5, 10); 29 | 30 | ret.times = times; 31 | ret.num = intNum; 32 | 33 | return ret; 34 | } 35 | 36 | /* 37 | * 核心方法,实现加减乘除运算,确保不丢失精度 38 | * 思路:把小数放大为整数(乘),进行算术运算,再缩小为小数(除) 39 | * 40 | * @param a {number} 运算数1 41 | * @param b {number} 运算数2 42 | * @param digits {number} 精度,保留的小数点数,比如 2, 即保留为两位小数 43 | * @param op {string} 运算类型,有加减乘除(add/subtract/multiply/divide) 44 | * 45 | */ 46 | function operation(a, b, op) { 47 | var o1 = toInteger(a); 48 | var o2 = toInteger(b); 49 | var n1 = o1.num; 50 | var n2 = o2.num; 51 | var t1 = o1.times; 52 | var t2 = o2.times; 53 | var max = t1 > t2 ? t1 : t2; 54 | var result = null; 55 | 56 | switch (op) { 57 | case 'add': 58 | if (t1 === t2) { // 两个小数位数相同 59 | result = n1 + n2; 60 | } 61 | else if (t1 > t2) { // o1 小数位 大于 o2 62 | result = n1 + n2 * (t1 / t2); 63 | } 64 | else { // o1 小数位 小于 o2 65 | result = n1 * (t2 / t1) + n2; 66 | } 67 | 68 | return result / max; 69 | case 'subtract': 70 | if (t1 === t2) { 71 | result = n1 - n2; 72 | } 73 | else if (t1 > t2) { 74 | result = n1 - n2 * (t1 / t2); 75 | } 76 | else { 77 | result = n1 * (t2 / t1) - n2; 78 | } 79 | 80 | return result / max; 81 | case 'multiply': 82 | result = (n1 * n2) / (t1 * t2); 83 | 84 | return result; 85 | case 'divide': 86 | return result = function () { 87 | var r1 = n1 / n2; 88 | var r2 = t2 / t1; 89 | return operation(r1, r2, 'multiply'); 90 | }(); 91 | } 92 | } 93 | /** 94 | * 做小数点的四舍五入计算 95 | * @param {*} num 96 | * @param {*} precision 97 | */ 98 | function fixed(num, precision) { 99 | if (!precision) { 100 | precision = 2; 101 | } 102 | if (!isRealNum(num)) return num; 103 | let s = num.toFixed(precision); 104 | let index = s.indexOf('.'); 105 | let prefix = s.substring(0, index); 106 | let suffix = s.substring(index + 1, s.length); 107 | if (suffix) { 108 | for (let i = suffix.length - 1; i != 0; i--) { 109 | //最末位不为0,直接break; 110 | if (suffix.charAt(i) != '0' && i == suffix.length - 1) { 111 | break; 112 | } else { 113 | suffix = suffix.substring(0, i); 114 | } 115 | } 116 | } 117 | return Number(prefix + '.' + suffix); 118 | } 119 | 120 | 121 | /** 122 | * Calculation +-/* Solve the problem of js accuracy 123 | */ 124 | Number.prototype.add = function (value) { 125 | let number = parseFloat(value); 126 | if (typeof number !== 'number' || Number.isNaN(number)) { 127 | throw new Error('请输入数字或者数字字符串~'); 128 | }; 129 | return operation(this, number, 'add'); 130 | }; 131 | Number.prototype.subtract = function (value) { 132 | let number = parseFloat(value); 133 | if (typeof number !== 'number' || Number.isNaN(number)) { 134 | throw new Error('请输入数字或者数字字符串~'); 135 | } 136 | return operation(this, number, 'subtract'); 137 | }; 138 | Number.prototype.multiply = function (value) { 139 | let number = parseFloat(value); 140 | if (typeof number !== 'number' || Number.isNaN(number)) { 141 | throw new Error('请输入数字或者数字字符串~'); 142 | } 143 | return operation(this, number, 'multiply'); 144 | }; 145 | Number.prototype.divide = function (value) { 146 | let number = parseFloat(value); 147 | if (typeof number !== 'number' || Number.isNaN(number)) { 148 | throw new Error('请输入数字或者数字字符串~'); 149 | } 150 | return operation(this, number, 'divide'); 151 | }; 152 | Number.prototype.tofixed = function (value) { 153 | let precision = parseFloat(value); 154 | if (typeof precision !== 'number' || Number.isNaN(precision)) { 155 | throw new Error('请输入数字或者数字字符串~'); 156 | } 157 | return fixed(this, precision); 158 | }; -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | const Store = { 2 | container: null, 3 | loadingObj:{}, 4 | luckysheetfile: null, 5 | defaultcolumnNum: 60, 6 | defaultrowNum: 84, 7 | fullscreenmode: true, 8 | devicePixelRatio: 1, 9 | 10 | currentSheetIndex: 0, 11 | calculateSheetIndex: 0, 12 | flowdata: [], 13 | config: {}, 14 | 15 | visibledatarow: [], 16 | visibledatacolumn: [], 17 | ch_width: 0, 18 | rh_height: 0, 19 | 20 | cellmainWidth: 0, 21 | cellmainHeight: 0, 22 | toolbarHeight: 0, 23 | infobarHeight: 0, 24 | calculatebarHeight: 0, 25 | rowHeaderWidth: 46, 26 | columnHeaderHeight: 20, 27 | cellMainSrollBarSize: 12, 28 | sheetBarHeight: 31, 29 | statisticBarHeight: 23, 30 | luckysheetTableContentHW: [0, 0], 31 | 32 | defaultcollen: 73, 33 | defaultrowlen: 19, 34 | 35 | jfcountfuncTimeout: null, 36 | jfautoscrollTimeout: null, 37 | 38 | luckysheet_select_status: false, 39 | luckysheet_select_save: [{ "row": [0, 0], "column": [0, 0] }], 40 | luckysheet_selection_range: [], 41 | 42 | luckysheet_copy_save: {}, //复制粘贴 43 | luckysheet_paste_iscut: false, 44 | 45 | filterchage: true, //筛选 46 | luckysheet_filter_save: { "row": [], "column": [] }, 47 | 48 | luckysheet_sheet_move_status: false, 49 | luckysheet_sheet_move_data: [], 50 | luckysheet_scroll_status: false, 51 | 52 | luckysheetisrefreshdetail: true, 53 | luckysheetisrefreshtheme: true, 54 | luckysheetcurrentisPivotTable: false, 55 | 56 | luckysheet_rows_selected_status: false, //行列标题相关参 57 | luckysheet_cols_selected_status: false, 58 | luckysheet_rows_change_size: false, 59 | luckysheet_rows_change_size_start: [], 60 | luckysheet_cols_change_size: false, 61 | luckysheet_cols_change_size_start: [], 62 | luckysheet_cols_dbclick_timeout: null, 63 | luckysheet_cols_dbclick_times: 0, 64 | 65 | luckysheetCellUpdate: [], 66 | 67 | luckysheet_shiftpositon: null, 68 | 69 | iscopyself: true, 70 | 71 | orderbyindex: 0, //排序下标 72 | 73 | luckysheet_model_move_state: false, //模态框拖动 74 | luckysheet_model_xy: [0, 0], 75 | luckysheet_model_move_obj: null, 76 | 77 | luckysheet_cell_selected_move: false, //选区拖动替换 78 | luckysheet_cell_selected_move_index: [], 79 | 80 | luckysheet_cell_selected_extend: false, //选区下拉 81 | luckysheet_cell_selected_extend_index: [], 82 | luckysheet_cell_selected_extend_time: null, 83 | 84 | clearjfundo: true, 85 | jfundo: [], 86 | jfredo: [], 87 | lang: 'en', //language 88 | createChart: '', 89 | highlightChart: '', 90 | zIndex: 15, 91 | chartparam: { 92 | luckysheetCurrentChart: null, //current chart_id 93 | luckysheetCurrentChartActive: false, 94 | luckysheetCurrentChartMove: null, // Debounce state 95 | luckysheetCurrentChartMoveTimeout: null,//拖动图表框的节流定时器 96 | luckysheetCurrentChartMoveObj: null, //chart DOM object 97 | luckysheetCurrentChartMoveXy: null, //上一次操作结束的图表信息,x,y: chart框位置,scrollLeft1,scrollTop1: 滚动条位置 98 | luckysheetCurrentChartMoveWinH: null, //左右滚动条滑动距离 99 | luckysheetCurrentChartMoveWinW: null, //上下滚动条滑动距离 100 | luckysheetCurrentChartResize: null, 101 | luckysheetCurrentChartResizeObj: null, 102 | luckysheetCurrentChartResizeXy: null, 103 | luckysheetCurrentChartResizeWinH: null, 104 | luckysheetCurrentChartResizeWinW: null, 105 | luckysheetInsertChartTosheetChange: true, // 正在执行撤销 106 | luckysheetCurrentChartZIndexRank : 100, 107 | luckysheet_chart_redo_click:false, //撤销重做时标识 108 | luckysheetCurrentChartMaxState: false, //图表全屏状态 109 | jfrefreshchartall: '', 110 | changeChartCellData: '', 111 | renderChart: '', 112 | getChartJson: '' 113 | }, 114 | functionList:null, //function list explanation 115 | luckysheet_function:null, 116 | chart_selection: {}, 117 | currentChart: '', 118 | scrollRefreshSwitch:true, 119 | 120 | measureTextCache:{}, 121 | measureTextCellInfoCache:{}, 122 | measureTextCacheTimeOut:null, 123 | cellOverflowMapCache:{}, 124 | 125 | zoomRatio:1, 126 | 127 | visibledatacolumn_unique:null, 128 | visibledatarow_unique:null, 129 | 130 | showGridLines:true, 131 | 132 | toobarObject: {}, //toolbar constant 133 | inlineStringEditCache:null, 134 | inlineStringEditRange:null, 135 | 136 | fontList:[], 137 | defaultFontSize: 10, 138 | 139 | currentSheetView:"viewNormal", 140 | 141 | // cooperative editing 142 | cooperativeEdit:{ 143 | usernameTimeout:{ 144 | 145 | }, 146 | changeCollaborationSize:[], //改变行高或者列宽时,协同提示框需要跟随改变所需数据 147 | allDataColumnlen:[],//列宽发生过改变的列 148 | merge_range:{},//合并时单元格信息 149 | checkoutData:[],//切换表格页时所需数据 150 | }, 151 | 152 | // Resources that currently need to be loaded asynchronously, especially plugins. 'Core' marks the core rendering process. 153 | asyncLoad:['core'], 154 | // 默认单元格 155 | defaultCell: { 156 | bg: null, 157 | bl: 0, 158 | ct: {fa: "General", t: "n"}, 159 | fc: "rgb(51, 51, 51)", 160 | ff: 0, 161 | fs: 11, 162 | ht: 1, 163 | it: 0, 164 | vt: 1, 165 | m: '', 166 | v: '' 167 | } 168 | 169 | } 170 | 171 | export default Store; -------------------------------------------------------------------------------- /src/global/dynamicArray.js: -------------------------------------------------------------------------------- 1 | import { getObjType } from '../utils/util'; 2 | import { getSheetIndex } from '../methods/get'; 3 | import Store from '../store'; 4 | 5 | //动态数组计算 6 | function dynamicArrayCompute(dynamicArray) { 7 | let dynamicArray_compute = {}; 8 | 9 | if(getObjType(dynamicArray) == "array"){ 10 | for(let i = 0; i < dynamicArray.length; i++){ 11 | let d_row = dynamicArray[i].r; 12 | let d_col = dynamicArray[i].c; 13 | let d_f = dynamicArray[i].f; 14 | 15 | if(Store.flowdata[d_row][d_col] != null && Store.flowdata[d_row][d_col].f != null && Store.flowdata[d_row][d_col].f == d_f){ 16 | if((d_row + "_" + d_col) in dynamicArray_compute){ 17 | dynamicArray_compute = dynamicArraySpillEditCompute(dynamicArray_compute, d_row , d_col); 18 | } 19 | 20 | let d_data = dynamicArray[i].data; 21 | let d_rowlen = d_data.length; 22 | let d_collen = 1; 23 | 24 | if(getObjType(d_data[0]) == "array"){ 25 | d_collen = d_data[0].length; 26 | } 27 | 28 | if(dynamicArrayRangeIsAllNull({ "row": [d_row, d_row + d_rowlen - 1], "column": [d_col, d_col + d_collen - 1] }, Store.flowdata)){ 29 | for(let x = 0; x < d_rowlen; x++){ 30 | for(let y = 0; y < d_collen; y++){ 31 | let rowIndex = d_row + x; 32 | let colIndex = d_col + y; 33 | 34 | if(getObjType(d_data[0]) == "array"){ 35 | dynamicArray_compute[rowIndex + "_" + colIndex] = {"v": d_data[x][y], "r": d_row, "c": d_col}; 36 | } 37 | else{ 38 | dynamicArray_compute[rowIndex + "_" + colIndex] = {"v": d_data[x], "r": d_row, "c": d_col}; 39 | } 40 | } 41 | } 42 | } 43 | else{ 44 | dynamicArray_compute[d_row + "_" + d_col] = {"v": "#SPILL!", "r": d_row, "c": d_col}; 45 | } 46 | } 47 | } 48 | } 49 | 50 | return dynamicArray_compute; 51 | } 52 | 53 | function dynamicArraySpillEditCompute(computeObj, r, c) { 54 | let rowIndex = computeObj[r + "_" + c].r; 55 | let colIndex = computeObj[r + "_" + c].c; 56 | 57 | for(let x in computeObj){ 58 | if(x == (rowIndex + "_" + colIndex)){ 59 | computeObj[x].v = "#SPILL!"; 60 | } 61 | else if(computeObj[x].r == rowIndex && computeObj[x].c == colIndex){ 62 | delete computeObj[x]; 63 | } 64 | } 65 | 66 | return computeObj; 67 | } 68 | 69 | //范围是否都是空单元格(除第一个单元格) 70 | function dynamicArrayRangeIsAllNull(range, data) { 71 | let r1 = range["row"][0], r2 = range["row"][1]; 72 | let c1 = range["column"][0], c2 = range["column"][1]; 73 | 74 | let isAllNull = true; 75 | for(let r = r1; r <= r2; r++){ 76 | for(let c = c1; c <= c2; c++){ 77 | if(!(r == r1 && c == c1) && data[r][c] != null && data[r][c].v != null && data[r][c].v.toString() != ""){ 78 | isAllNull = false; 79 | break; 80 | } 81 | } 82 | } 83 | 84 | return isAllNull; 85 | } 86 | 87 | //点击表格区域是否属于动态数组区域 88 | function dynamicArrayHightShow(r, c) { 89 | let dynamicArray = Store.luckysheetfile[getSheetIndex(Store.currentSheetIndex)]["dynamicArray"] == null ? [] : Store.luckysheetfile[getSheetIndex(Store.currentSheetIndex)]["dynamicArray"]; 90 | let dynamicArray_compute = dynamicArrayCompute(dynamicArray); 91 | 92 | if((r + "_" + c) in dynamicArray_compute && dynamicArray_compute[r + "_" + c].v != "#SPILL!"){ 93 | let d_row = dynamicArray_compute[r + "_" + c].r; 94 | let d_col = dynamicArray_compute[r + "_" + c].c; 95 | 96 | let d_f = Store.flowdata[d_row][d_col].f; 97 | 98 | let rlen, clen; 99 | for(let i = 0; i < dynamicArray.length; i++){ 100 | if(dynamicArray[i].f == d_f){ 101 | rlen = dynamicArray[i].data.length; 102 | 103 | if(getObjType(dynamicArray[i].data[0]) == "array"){ 104 | clen = dynamicArray[i].data[0].length; 105 | } 106 | else{ 107 | clen = 1; 108 | } 109 | } 110 | } 111 | 112 | let d_row_end = d_row + rlen - 1; 113 | let d_col_end = d_col + clen - 1; 114 | 115 | let row = Store.visibledatarow[d_row_end], 116 | row_pre = d_row - 1 == -1 ? 0 : Store.visibledatarow[d_row - 1]; 117 | let col = Store.visibledatacolumn[d_col_end], 118 | col_pre = d_col - 1 == -1 ? 0 : Store.visibledatacolumn[d_col - 1]; 119 | 120 | $("#luckysheet-dynamicArray-hightShow").css({ 121 | "left": col_pre, 122 | "width": col - col_pre - 1, 123 | "top": row_pre, 124 | "height": row - row_pre - 1, 125 | "display": "block" 126 | }); 127 | } 128 | else{ 129 | $("#luckysheet-dynamicArray-hightShow").hide(); 130 | } 131 | } 132 | 133 | export { 134 | dynamicArrayCompute, 135 | dynamicArraySpillEditCompute, 136 | dynamicArrayRangeIsAllNull, 137 | dynamicArrayHightShow, 138 | } -------------------------------------------------------------------------------- /docs/zh/guide/contribute.md: -------------------------------------------------------------------------------- 1 | 2 | # 贡献指南 3 | 4 | 欢迎!我们很高兴您能来到这里,并非常期待您能有兴趣参与 Luckysheet 贡献。当然,在您参与 Luckysheet 贡献之前,请确保通读以下全文: 5 | 6 | ## 我们的行为准则 7 | 8 | 1. 我们保证尊重所有参与贡献的人,不限于提出问题、文档和代码贡献、解决bug以及其它贡献的人; 9 | 10 | 2. 我们有义务遵守当地法律法规,所有的附带法律风险的行为我们都是拒绝的; 11 | 3. 我们反对任何参与者存在贬损评论、人身攻击、骚扰或侮辱他人以及其他非专业行为; 12 | 4. 我们有权并有责任删除或编辑与此行为准则不符的内容,不限于代码、Issues、wiki、文档以及其它。不遵守行为准则的参与者可能会被移除团队; 13 | 5. 我们接受任何人的监督,任何人可通过问题反馈,向我们报告发现的与此行为准则不符的事实存在。 14 | 15 | ## 如何参与贡献? 16 | 17 | * 贡献文档:浏览文档可以加深您对 Luckysheet 的了解,一旦发现文档写得不清晰或逻辑混乱的地方,可以订正、修改、补充,您可以通过 [中文论坛](https://support.qq.com/products/288322)或者 [谷歌论坛](https://groups.google.com/g/luckysheet)给予反馈 18 | * 贡献代码:欢迎大家为 Luckysheet 社区贡献代码,欢迎您认领Open状态的 [Issues](https://github.com/mengshukeji/Luckysheet/issues) 和未完成的特性,提交PR,成为贡献者之一如果您在使用过程中发现有些功能无法满足您的需求或出现问题,请在Issues中记录 19 | * 参与Issue讨论:您可以在任一 [Issues](https://github.com/mengshukeji/Luckysheet/issues) 下发表您的建议 20 | * Review代码:您可以在 [Github](https://github.com/mengshukeji/Luckysheet)上看到所有贡献者提交的PR,您可以Review他们的代码并发表您的建议 21 | 22 | 23 | ## 如何提交 Issues 24 | 25 | 在您提交特性/改进前,应该注意以下几点: 26 | 27 | * 请先确认该特性/改进是否被其他人已经提交 28 | * 一个通俗易懂的标题来阐述你提交的Bug/提交特性/改进 29 | * 如果是Bug则详细描述该bug产生的原因,如果能够复现,请尽量提供完整的重现步骤 30 | * 如果是特性,那么该特性应该有广泛的适用性,适用于大部分用户,最好能够提供详尽的设计文档 31 | * 如果是改进,尽可能描述清楚此改进所带来的益处 32 | 33 | 具体步骤: 34 | 35 | * 创建 [Issues](https://github.com/mengshukeji/Luckysheet/issues) ,描述清楚问题 36 | * 如果你要解决该issue则将issue assign到自己名下,如果你仅仅是提交Bug/特性/改进,并没有时间去贡献代码,则assignne设置为空 37 | * 如果是比较大的特性/改进,尽量先输出设计文档,走 [Luckysheet RFC](https://github.com/mengshukeji/Luckysheet-rfcs) 流程,供其他人review 38 | 39 | ## 如何认领 Issues 40 | 41 | 在 Luckysheet 的 [Issues](https://github.com/mengshukeji/Luckysheet/issues) 列表中,有很多由其他人创建的issue并未被修复,如果你感兴趣的话,可以认领这些issue。认领步骤如下: 42 | 43 | * 在该issue下留言,表达想认领该任务的想法,另注明 **@I can solve it** 即可 44 | * 如果提交者没有意见,则将该issue assign到自己名下并及时更新进度 45 | * 如果是比较大的特性,尽量先输出设计文档,走 [Luckysheet RFC](https://github.com/mengshukeji/Luckysheet-rfcs) 流程,供其他人review 46 | * 开发代码并提交代码至github 47 | 48 | 49 | ## 如何提交代码 50 | 51 | 1. fork 到自己的仓库 52 | 53 | 进入  [Luckysheet](https://github.com/mengshukeji/Luckysheet)  的Github页面 ,点击右上角按钮 Fork 进行 Fork。 54 | 55 | 2. git clone 到本地 56 | 57 | ```shell 58 | git clone https://github.com//Luckysheet.git 59 | ``` 60 | 61 | 3. 上游建立连接 62 | 63 | ```shell 64 | 65 | cd Luckysheet 66 | git remote add upstream https://github.com/mengshukeji/Luckysheet.git 67 | ``` 68 | 4. 创建开发分支 69 | 70 | ```shell 71 | git checkout -b dev 72 | ``` 73 | 74 | 5. 修改提交代码 75 | 76 | ```shell 77 | git add .  78 | npm run commit 79 | git push origin dev 80 | ``` 81 | 82 | 6. 同步代码,将最新代码同步到本地 83 | 84 | ```shell 85 | git fetch upstream  86 | git rebase upstream/master 87 | ``` 88 | 89 | 7. 如果有冲突(没有可以忽略) 90 | 91 | ```shell 92 | git status # 查看冲突文件,并修改冲突 93 | git add . 94 | git rebase --continue 95 | ``` 96 | 提交git rebase --continue命令的时候,如果弹出vim提示编辑commit信息,则可以添加你的修改,然后保存退出 97 | > vim命令请参考阅读[vim](https://www.runoob.com/linux/linux-vim.html) 98 | 99 | 8. 提交分支代码 100 | 101 | ```shell 102 | git push origin dev 103 | ``` 104 | 105 | 如果提示需要先pull 可以先拉取在提交 106 | ```shell 107 | git pull origin dev 108 | git push origin dev 109 | ``` 110 | 若弹出vim提示编辑commit信息,可以直接通过vim命令退出 111 | > vim命令请参考阅读[vim](https://www.runoob.com/linux/linux-vim.html) 112 | 113 | 9. 提交pr 114 | 去自己github仓库对应fork的项目,切换到刚刚创建修改的分支,点击new pull request,并添加上对应的描述,最后点击Create pull request进行提交 115 | 116 | ## 代码规范 117 | 118 | > 一般性的代码规范示例 119 | 120 | * 保持块深度最小。尽可能避免嵌套If条件 121 | ```js 122 | // CORRECT 123 | if (!comparison) return 124 | 125 | if (variable) { 126 | for (const item of items) {} 127 | } else if (variable2) { 128 | // Do something here 129 | } 130 | 131 | // INCORRECT 132 | if (comparison) { 133 | if (variable) { 134 | for (const item in items) {} 135 | } else if (variable2) { 136 | // Do something here 137 | } 138 | } else { 139 | return 140 | } 141 | ``` 142 | 143 | * 不要使用操作数进行链比较 144 | ```js 145 | // CORRECT 146 | 147 | if (cb) cb() 148 | if (!cb || (cb === fn)) cb() 149 | 150 | // INCORRECT 151 | 152 | cb && cb() 153 | (!cb || (cb === fn)) && cb() 154 | ``` 155 | 156 | * 所有变量都应该按字母顺序在块的开头声明 157 | ```js 158 | // CORRECT 159 | function foo () { 160 | const foo = 'bar' 161 | const bar = 'foo' 162 | 163 | if (conditional) {} 164 | 165 | ... 166 | 167 | return foo 168 | } 169 | 170 | // INCORRECT 171 | 172 | function foo () { 173 | const foo = 'bar' 174 | 175 | if (conditional) {} 176 | 177 | const bar = 'foo' 178 | 179 | ... 180 | 181 | return foo 182 | } 183 | ``` 184 | 185 | * 尽快返回 186 | ```js 187 | // CORRECT 188 | if (condition) return 'foo' 189 | if (condition2) return 'bar' 190 | // Return must have a blank line above 191 | return 'fizz' 192 | 193 | // INCORRECT 194 | const variable = '' 195 | 196 | if (condition) { 197 | variable = 'foo' 198 | } else if (condition2) { 199 | variable = 'bar' 200 | } else { 201 | variable = 'fizz' 202 | } 203 | 204 | return variable 205 | ``` 206 | 207 | ## 如何贡献文档 208 | 209 | ## 如何成为Luckysheet Committer 210 | 211 | 任何人只要对 Luckysheet 项目做了贡献,那您就是官方承认的 Luckysheet 项目的Contributor了,从Contributor成长为Committer并没有一个确切的标准, 也没有任何预期的时间表,但是Committer的候选人一般都是长期活跃的贡献者,成为Committer并没有要求必须有巨大的架构改进贡献, 或者多少行的代码贡献,贡献代码、贡献文档、参与邮件列表的讨论、帮助回答问题等等都提升自己影响力的方式。 212 | 213 | 潜在贡献清单(无特定顺序): 214 | 215 | * 提交自己发现的Bug、特性、改进到issue 216 | * 更新官方文档使项目的文档是最近的、撰写 Luckysheet 的最佳实践、特性剖析的各种对用户有用的文档 217 | * 执行测试并报告测试结果,性能测试与其他MQ的性能对比测试等 218 | * 审查(Review)其他人的工作(包括代码和非代码)并发表你自己的建议 219 | * 指导新加入的贡献者,熟悉社区流程 220 | * 发表关于 Luckysheet 的博客 221 | * 有利于 Luckysheet 社区发展的任何贡献 222 | * ...... 223 | -------------------------------------------------------------------------------- /src/css/luckysheet-zoom.css: -------------------------------------------------------------------------------- 1 | .luckysheet-zoom-content{ 2 | position: relative; 3 | float: right; 4 | width:210px; 5 | /* right: 0px; */ 6 | height: 22px; 7 | line-height: 22px; 8 | text-align: right; 9 | padding-right: 10px; 10 | white-space: nowrap; 11 | overflow: hidden; 12 | display: flex; 13 | align-items: center; 14 | user-select: none; 15 | } 16 | 17 | .luckysheet-zoom-content .luckysheet-zoom-minus{ 18 | position: absolute; 19 | top: 0; 20 | left: 0px; 21 | width: 20px; 22 | height:20px; 23 | cursor: pointer; 24 | display: flex; 25 | align-items: center; 26 | justify-content: center; 27 | } 28 | 29 | .luckysheet-zoom-content .luckysheet-zoom-minus-icon{ 30 | background-image:url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB3aWR0aD0iMTRweCIgaGVpZ2h0PSIycHgiIHZpZXdCb3g9IjAgMCAxNCAyIiB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPgogICAgPCEtLSBHZW5lcmF0b3I6IFNrZXRjaCA2MyAoOTI0NDUpIC0gaHR0cHM6Ly9za2V0Y2guY29tIC0tPgogICAgPHRpdGxlPnJpcWlxdWppYW7lpIfku70gNDU8L3RpdGxlPgogICAgPGRlc2M+Q3JlYXRlZCB3aXRoIFNrZXRjaC48L2Rlc2M+CiAgICA8ZyBpZD0iMjAyMC8wOC8xNCIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjEiIGZpbGw9Im5vbmUiIGZpbGwtcnVsZT0iZXZlbm9kZCI+CiAgICAgICAgPGcgaWQ9IueUu+adv+Wkh+S7vS0yIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMTcwNC4wMDAwMDAsIC0xMDY0LjAwMDAwMCkiIGZpbGw9IiM0NDRENUEiPgogICAgICAgICAgICA8ZyBpZD0icmlxaXF1amlhbuWkh+S7vS0xMjYiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDE2OTkuMDAwMDAwLCAxMDUzLjAwMDAwMCkiPgogICAgICAgICAgICAgICAgPGcgaWQ9Iue8lue7hCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNS4wMDAwMDAsIDExLjAwMDAwMCkiPgogICAgICAgICAgICAgICAgICAgIDxyZWN0IGlkPSLnn6nlvaIiIHg9IjAiIHk9IjAiIHdpZHRoPSIxNCIgaGVpZ2h0PSIyIj48L3JlY3Q+CiAgICAgICAgICAgICAgICA8L2c+CiAgICAgICAgICAgIDwvZz4KICAgICAgICA8L2c+CiAgICA8L2c+Cjwvc3ZnPg=='); 31 | width: 14px; 32 | height: 2px; 33 | } 34 | 35 | .luckysheet-zoom-content .luckysheet-zoom-minus:hover{ 36 | background-color: #E1E4E8; 37 | } 38 | 39 | .luckysheet-zoom-content .luckysheet-zoom-slider{ 40 | position: absolute; 41 | top: 0; 42 | left: 25px; 43 | width: 100px; 44 | height: 100%; 45 | display: flex; 46 | align-items: center; 47 | } 48 | 49 | 50 | .luckysheet-zoom-content .luckysheet-zoom-slider .luckysheet-zoom-line{ 51 | position: absolute; 52 | top: 10px; 53 | width: 100px; 54 | height: 2px; 55 | background: #E1E4E8; 56 | } 57 | 58 | .luckysheet-zoom-content .luckysheet-zoom-slider .luckysheet-zoom-cursor{ 59 | position: absolute; 60 | top: 7px; 61 | width: 8px; 62 | height: 8px; 63 | border-radius: 8px; 64 | background: #B5BDB8; 65 | cursor: pointer; 66 | z-index: 2; 67 | transition: all 0.3s; 68 | } 69 | 70 | .luckysheet-zoom-content .luckysheet-zoom-slider .luckysheet-zoom-cursor:hover{ 71 | transform: scale(1.2); 72 | transform-origin: center center; 73 | background: rgb(160, 160, 160); 74 | } 75 | 76 | .luckysheet-zoom-content .luckysheet-zoom-slider .luckysheet-zoom-hundred{ 77 | position: absolute; 78 | top: 9px; 79 | width: 2px; 80 | height: 4px; 81 | left: 49px; 82 | background: #1E1E1F; 83 | } 84 | 85 | 86 | .luckysheet-zoom-content .luckysheet-zoom-plus{ 87 | position: absolute; 88 | top: 0; 89 | left: 130px; 90 | width: 20px; 91 | height:20px; 92 | cursor: pointer; 93 | display: flex; 94 | align-items: center; 95 | justify-content: center; 96 | } 97 | 98 | .luckysheet-zoom-content .luckysheet-zoom-plus .luckysheet-zoom-plus-icon{ 99 | background-image:url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB3aWR0aD0iMTRweCIgaGVpZ2h0PSIxNHB4IiB2aWV3Qm94PSIwIDAgMTQgMTQiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+CiAgICA8IS0tIEdlbmVyYXRvcjogU2tldGNoIDYzICg5MjQ0NSkgLSBodHRwczovL3NrZXRjaC5jb20gLS0+CiAgICA8dGl0bGU+cmlxaXF1amlhbuWkh+S7vSA0NjwvdGl0bGU+CiAgICA8ZGVzYz5DcmVhdGVkIHdpdGggU2tldGNoLjwvZGVzYz4KICAgIDxnIGlkPSIyMDIwLzA4LzE0IiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMSIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj4KICAgICAgICA8ZyBpZD0i55S75p2/5aSH5Lu9LTIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0xODQ4LjAwMDAwMCwgLTEwNTguMDAwMDAwKSIgZmlsbD0iIzQ0NEQ1QSI+CiAgICAgICAgICAgIDxnIGlkPSJyaXFpcXVqaWFu5aSH5Lu9LTExOSIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTg0My4wMDAwMDAsIDEwNTMuMDAwMDAwKSI+CiAgICAgICAgICAgICAgICA8ZyBpZD0i57yW57uEIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSg1LjAwMDAwMCwgNS4wMDAwMDApIj4KICAgICAgICAgICAgICAgICAgICA8cmVjdCBpZD0i55+p5b2iIiB4PSIwIiB5PSI2IiB3aWR0aD0iMTQiIGhlaWdodD0iMiI+PC9yZWN0PgogICAgICAgICAgICAgICAgICAgIDxyZWN0IGlkPSLnn6nlvaLlpIfku70iIHRyYW5zZm9ybT0idHJhbnNsYXRlKDcuMDAwMDAwLCA3LjAwMDAwMCkgcm90YXRlKC0yNzAuMDAwMDAwKSB0cmFuc2xhdGUoLTcuMDAwMDAwLCAtNy4wMDAwMDApICIgeD0iMCIgeT0iNiIgd2lkdGg9IjE0IiBoZWlnaHQ9IjIiPjwvcmVjdD4KICAgICAgICAgICAgICAgIDwvZz4KICAgICAgICAgICAgPC9nPgogICAgICAgIDwvZz4KICAgIDwvZz4KPC9zdmc+'); 100 | width: 14px; 101 | height: 14px; 102 | } 103 | 104 | .luckysheet-zoom-content .luckysheet-zoom-plus:hover{ 105 | background-color: #E1E4E8; 106 | } 107 | 108 | .luckysheet-zoom-content .luckysheet-zoom-ratioText{ 109 | position: absolute; 110 | top: 0; 111 | left: 155px; 112 | width: 60px; 113 | color: #1E1E1F; 114 | font-size: 12px; 115 | text-align: left; 116 | cursor: pointer; 117 | } 118 | 119 | .luckysheet-zoom-content .luckysheet-zoom-ratioText:hover{ 120 | background-color: #E1E4E8; 121 | } -------------------------------------------------------------------------------- /src/global/cursorPos.js: -------------------------------------------------------------------------------- 1 | import Store from '../store'; 2 | 3 | function luckysheetRangeLast(obj) { 4 | let range; 5 | 6 | if(document.createRange){ //chrome, firefox, opera, safari, ie9+ 7 | if(obj.innerHTML != obj.innerText || obj.innerHTML == ""){ 8 | obj.focus(); //解决ff不获取焦点无法定位问题 9 | range = window.getSelection();//创建range 10 | range.selectAllChildren(obj);//range 选择obj下所有子内容 11 | range.collapseToEnd();//光标移至最后 12 | } 13 | else{ 14 | let len = obj.innerText.length; 15 | 16 | range = document.createRange(); 17 | range.selectNodeContents(obj); 18 | range.setStart(obj.childNodes[0], len); 19 | range.collapse(true); 20 | 21 | let selection = window.getSelection(); 22 | selection.removeAllRanges(); 23 | selection.addRange(range); 24 | } 25 | } 26 | else if(document.selection){ //ie8 and lower 27 | range = document.body.createTextRange(); 28 | range.moveToElementText(obj); 29 | range.collapse(false); 30 | range.select(); 31 | } 32 | } 33 | 34 | function getCursortPosition(textDom){ 35 | let cursorPos = 0; 36 | 37 | if(document.selection){ 38 | textDom.focus(); 39 | let selectRange = document.selection.createRange(); 40 | selectRange.moveStart("character", -textDom.value.length); 41 | cursorPos = selectRange.text.length; 42 | } 43 | else if(textDom.selectionStart || textDom.selectionStart == "0"){ 44 | cursorPos = textDom.selectionStart; 45 | } 46 | 47 | return cursorPos; 48 | } 49 | 50 | function hideMenuByCancel(event){ 51 | 52 | // Right-click the menu in the title bar, and click on the elements whose class is luckysheet-cols-rows-shift-left and luckysheet-cols-rows-shift-right will trigger the hiding of the menu bar. It should be prohibited. Exclude these two elements. There may be more Other elements will also jump here for more testing 53 | 54 | if(event.target.classList && (event.target.classList.contains('luckysheet-cols-rows-shift-left') || event.target.classList.contains('luckysheet-cols-rows-shift-right'))){ 55 | return; 56 | } 57 | 58 | if (!$(event.target).hasClass("luckysheet-mousedown-cancel") && $(event.target).filter("[class*='sp-palette']").length == 0 && $(event.target).filter("[class*='sp-thumb']").length == 0 && $(event.target).filter("[class*='sp-']").length == 0) { 59 | $("#luckysheet-rightclick-menu").hide(); 60 | $("#luckysheet-cols-h-hover").hide(); 61 | $("#luckysheet-cols-menu-btn").hide(); 62 | // $("#luckysheet-rightclick-menu").hide(); 63 | $("#luckysheet-sheet-list, #luckysheet-rightclick-sheet-menu, #luckysheet-user-menu").hide(); 64 | $("body > .luckysheet-filter-menu, body > .luckysheet-filter-submenu, body > .luckysheet-cols-menu").hide(); 65 | //$("body > luckysheet-menuButton").hide(); 66 | Store.luckysheet_cols_menu_status = false; 67 | } 68 | } 69 | 70 | function selectTextDom(ele){ 71 | if (window.getSelection) { 72 | let range = document.createRange(); 73 | range.selectNodeContents(ele); 74 | if(range.startContainer && isInPage(range.startContainer)){ 75 | window.getSelection().removeAllRanges(); 76 | window.getSelection().addRange(range); 77 | } 78 | } 79 | else if (document.selection) { 80 | let range = document.body.createTextRange(); 81 | range.moveToElementText(ele); 82 | range.select(); 83 | } 84 | } 85 | 86 | function selectTextContent(ele){ 87 | if (window.getSelection) { 88 | let range = document.createRange(); 89 | var content=ele.firstChild; 90 | range.setStart(content,0); 91 | range.setEnd(content,content.length); 92 | if(range.startContainer && isInPage(range.startContainer)){ 93 | window.getSelection().removeAllRanges(); 94 | window.getSelection().addRange(range); 95 | } 96 | } 97 | else if (document.selection) { 98 | let range = document.body.createTextRange(); 99 | range.moveToElementText(ele); 100 | range.select(); 101 | } 102 | } 103 | 104 | function selectTextContentCross(sEle, eEle){ 105 | if (window.getSelection) { 106 | let range = document.createRange(); 107 | var sContent=sEle.firstChild, eContent=eEle.firstChild; 108 | range.setStart(sContent,0); 109 | range.setEnd(eContent,eContent.length); 110 | if(range.startContainer && isInPage(range.startContainer)){ 111 | window.getSelection().removeAllRanges(); 112 | window.getSelection().addRange(range); 113 | } 114 | } 115 | } 116 | 117 | function selectTextContentCollapse(sEle, index){ 118 | if (window.getSelection) { 119 | let range = document.createRange(); 120 | var sContent=sEle.firstChild; 121 | if(index>sContent.length){ 122 | index=sContent.length; 123 | } 124 | else if(index<0){ 125 | index = 0; 126 | } 127 | range.setStart(sContent,index); 128 | range.collapse(true); 129 | if(range.startContainer && isInPage(range.startContainer)){ 130 | window.getSelection().removeAllRanges(); 131 | window.getSelection().addRange(range); 132 | } 133 | 134 | } 135 | } 136 | 137 | function isInPage(node) { 138 | return (node === document.body) ? false : document.body.contains(node); 139 | } 140 | 141 | export { 142 | luckysheetRangeLast, 143 | getCursortPosition, 144 | hideMenuByCancel, 145 | selectTextContent, 146 | selectTextDom, 147 | selectTextContentCross, 148 | selectTextContentCollapse 149 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Luckysheet 3.x is currently being rewritten in Typescript 2 | 3 | ## Introduction 4 | 🚀Luckysheet is an online spreadsheet like excel that is powerful, simple to configure, and completely open source. 5 | 6 | ## Ecosystem 7 | 8 | | Project | Description | 9 | |---------|-------------| 10 | | [Luckysheet Vue] | Luckysheet and Luckyexcel in a vue cli3 project | 11 | | [Luckysheet Vue3] | Luckysheet and Luckyexcel in a vue3 project with vite| 12 | | [Luckysheet React] | Luckysheet in a React project | 13 | | [Luckyexcel Node] | Use Luckyexcel in koa2 | 14 | | [Luckysheet Server] | Java backend Luckysheet Server | 15 | | [Luckysheet Server Starter] | LuckysheetServer docker deployment startup template | 16 | 17 | 18 | ## Features 19 | 20 | - **Formatting**: style, conditional formatting, text alignment and rotation, text truncation, overflow, automatic line wrapping, multiple data types, cell segmentation style 21 | - **Cells**: drag and drop, fill handle, multiple selection, find and replace, location, merge cells, data verification 22 | - **Row & column**: hide, insert, delete rows or columns, freeze, and split text 23 | - **Operation**: undo, redo, copy, paste, cut, hot key, format painter, drag and drop selection 24 | - **Formulas & Functions**: Built-in, remote and custom formulas 25 | - **Tables**: filter, sort 26 | - **Enhanced functions**: Pivot tables, charts, comments, cooperative editing, insert picture, matrix calculations, screenshots, copying to other formats, EXCEL import and export, etc. 27 | 28 | 29 | ## 📜 Changelog 30 | 31 | Detailed changes for each release are documented in the [CHANGELOG.md](CHANGELOG.md). 32 | 33 | ## Usage 34 | 35 | ### First step 36 | Introduce dependencies through CDN 37 | ``` 38 | 39 | 40 | 41 | 42 | 43 | 44 | ``` 45 | ### Second step 46 | Specify a table container 47 | ``` 48 |
49 | ``` 50 | ### Third step 51 | Create a table 52 | ``` 53 | 62 | ``` 63 | 64 | ## Development 65 | 66 | ### Requirements 67 | [Node.js](https://nodejs.org/en/) Version >= 6 68 | 69 | ### Installation 70 | ``` 71 | npm install 72 | npm install gulp -g 73 | ``` 74 | ### Development 75 | ``` 76 | npm run dev 77 | ``` 78 | ### Package 79 | ``` 80 | npm run build 81 | ``` 82 | 83 | ## Partner project 84 | 85 | - [luban-h5](https://github.com/ly525/luban-h5) 86 | - [h5-Dooring](https://github.com/MrXujiang/h5-Dooring) 87 | - [Furion](https://gitee.com/monksoul/Furion) 88 | - [AFFiNE.PRO](https://github.com/toeverything/AFFiNE) 89 | 90 | ## Communication 91 | - [Github Discussions](https://github.com/mengshukeji/Luckysheet/discussions) 92 | - [Gitter](https://gitter.im/mengshukeji/Luckysheet) 93 | 94 | [Chinese community](./README-zh.md) 95 | 96 | ## Sponsor 97 | 98 | Luckysheet is an MIT-licensed open source project with its ongoing development made possible entirely by the support of these awesome [backers](https://mengshukeji.github.io/LuckysheetDocs/about/sponsor.html#sponsors-list). If you'd like to join them, please consider: 99 | 100 | - [Become a backer or sponsor on Open Collective](https://opencollective.com/luckysheet). 101 | - One-time donation via PayPal, WeChat or Alipay 102 | 103 | 104 | ### What's the difference between Patreon and OpenCollective? 105 | 106 | Funds donated via Patreon go directly to support mengshukeji's work on Luckysheet. Funds donated via OpenCollective are managed with transparent expenses and will be used for compensating work and expenses for core team members or sponsoring community events. Your name/logo will receive proper recognition and exposure by donating on either platform. 107 | 108 | ### Active Core Team Members 109 | - [@wbfsa](https://github.com/wbfsa) 110 | - [@eiji-th](https://github.com/eiji-th) 111 | - [@fly-95](https://github.com/fly-95) 112 | - [@tonytonychopper123](https://github.com/tonytonychopper123) 113 | - [@Dushusir](https://github.com/Dushusir) 114 | - [@iamxuchen800117](https://github.com/iamxuchen800117) 115 | - [@wpxp123456](https://github.com/wpxp123456) 116 | - [@c19c19i](https://weibo.com/u/3884623955) 117 | - [@zhangchen915](https://github.com/zhangchen915) 118 | - [@jerry-f](https://github.com/jerry-f) 119 | - [@flowerField](https://github.com/flowerField) 120 | 121 | ### Community Partners 122 | - [@yiwasheng](https://github.com/yiwasheng) 123 | - [@danielcai1987](https://github.com/danielcai1987) 124 | - [@qq6690876](https://github.com/qq6690876) 125 | - [@javahuang](https://github.com/javahuang) 126 | - [@TimerGang](https://github.com/TimerGang) 127 | - [@gsw945](https://github.com/gsw945) 128 | - [@swen-xiong](https://github.com/swen-xiong) 129 | - [@lzmch](https://github.com/lzmch) 130 | - [@kdevilpf](https://github.com/kdevilpf) 131 | - [@WJWM0316](https://github.com/WJWM0316) 132 | 133 | ## License 134 | [MIT](http://opensource.org/licenses/MIT) 135 | 136 | Copyright (c) 2020-present, blue-bigtech 137 | -------------------------------------------------------------------------------- /src/global/createdom.js: -------------------------------------------------------------------------------- 1 | import { 2 | gridHTML, 3 | menuToolBar, 4 | flow, 5 | columnHeaderHTML, 6 | maskHTML, 7 | colsmenuHTML, 8 | rightclickHTML, 9 | inputHTML, 10 | filtermenuHTML, 11 | filtersubmenuHTML, 12 | sheetconfigHTML, 13 | } from '../controllers/constant'; 14 | import luckysheetConfigsetting from '../controllers/luckysheetConfigsetting'; 15 | import luckysheetPostil from '../controllers/postil'; 16 | import { datagridgrowth } from './getdata'; 17 | import editor from './editor'; 18 | import rhchInit from './rhchInit'; 19 | import { replaceHtml } from '../utils/util'; 20 | import Store from '../store'; 21 | import locale from '../locale/locale'; 22 | 23 | export default function luckysheetcreatedom(colwidth, rowheight, data, menu, title) { 24 | // //最少30行 25 | // if(rowheight < 30){ 26 | // rowheight = 30; 27 | // } 28 | 29 | // //最少22列 30 | // if(colwidth < 22){ 31 | // colwidth = 22; 32 | // } 33 | 34 | let gh = gridHTML(); 35 | gh = replaceHtml(gh, { "logotitle": title });//设置title 36 | gh = replaceHtml(gh, { "menu": menuToolBar() });//设置需要显示的菜单 37 | 38 | // if (data.length == 0) { 39 | // Store.flowdata = datagridgrowth(data, rowheight, colwidth); 40 | // } 41 | // else if (data.length < rowheight && data[0].length < colwidth) { 42 | // Store.flowdata = datagridgrowth(data, rowheight - data.length, colwidth - data[0].length); 43 | // } 44 | // else if (data.length < rowheight) { 45 | // Store.flowdata = datagridgrowth(data, rowheight - data.length, 0); 46 | // } 47 | // else if (data[0].length < colwidth) { 48 | // Store.flowdata = datagridgrowth(data, 0, colwidth - data[0].length); 49 | // } 50 | // else { 51 | // Store.flowdata = data; 52 | // } 53 | 54 | let flowHTML = flow; 55 | if(Store.config == null){ 56 | Store.config = {}; 57 | } 58 | 59 | rhchInit(rowheight, colwidth); 60 | 61 | const _locale = locale(); 62 | const locale_info = _locale.info; 63 | 64 | let addControll = ''+ locale_info.row +'('+locale_info.addLast+')'; 65 | let backControll = ' '; 66 | // let pageControll = ' 共'+ luckysheetConfigsetting.pageInfo.totalPage +'页,当前已显示'+ (luckysheetConfigsetting.pageInfo.currentPage) +'页,每页'+ luckysheetConfigsetting.pageInfo.pageSize +'条 '; 67 | let pageInfo = replaceHtml(locale_info.pageInfo,{ 68 | total:luckysheetConfigsetting.total?luckysheetConfigsetting.total:"", 69 | totalPage:luckysheetConfigsetting.pageInfo.totalPage?luckysheetConfigsetting.pageInfo.totalPage:"", 70 | currentPage:luckysheetConfigsetting.pageInfo.currentPage?luckysheetConfigsetting.pageInfo.currentPage:"", 71 | }); 72 | let pageControll = ' '+ pageInfo +' '; 73 | let pageControll2 = ' '+pageInfo+''; 74 | 75 | let bottomControll = ""; 76 | if(luckysheetConfigsetting.enableAddRow){ 77 | bottomControll += addControll; 78 | } 79 | 80 | if(luckysheetConfigsetting.enablePage){ 81 | if(parseInt(luckysheetConfigsetting.pageInfo.totalPage) == 1){ 82 | bottomControll += pageControll2; 83 | } 84 | else{ 85 | bottomControll += pageControll; 86 | } 87 | } 88 | 89 | if(luckysheetConfigsetting.enableAddBackTop){ 90 | bottomControll += backControll; 91 | } 92 | 93 | let flowstr = replaceHtml('
'+ bottomControll +'
', { "height": Store.rh_height, "width": Store.ch_width - 1 }); 94 | 95 | let colsheader = replaceHtml(columnHeaderHTML, { "width": Store.ch_width, "index": 0, "column": "" }); 96 | 97 | flowHTML = replaceHtml(flowHTML, { "width": Store.ch_width, "flow": flowstr, "index": 0 }); 98 | 99 | gh = replaceHtml(gh, { "flow": flowHTML, "rowHeader": "
", "columnHeader": colsheader, "functionButton": luckysheetConfigsetting.functionButton });//设置需要显示的菜单 100 | 101 | $("#" + Store.container).append(gh); 102 | 103 | $("#luckysheet-scrollbar-x div").width(Store.ch_width); 104 | $("#luckysheet-scrollbar-y div").height(Store.rh_height + Store.columnHeaderHeight - Store.cellMainSrollBarSize - 3); 105 | 106 | //新建行菜单 107 | $("body").first().append(maskHTML); 108 | $("body").first().append(colsmenuHTML); 109 | $("body").first().append(rightclickHTML()); 110 | $("body").first().append(inputHTML); 111 | $("body").first().append(replaceHtml(filtermenuHTML(), { "menuid": "filter" })); 112 | $("body").first().append(replaceHtml(filtersubmenuHTML(), { "menuid": "filter" })); 113 | $("body").first().append(sheetconfigHTML()); 114 | 115 | $("#luckysheet-rows-h").width((Store.rowHeaderWidth-1.5)); 116 | $("#luckysheet-cols-h-c").height((Store.columnHeaderHeight-1.5)); 117 | $("#luckysheet-left-top").css({width:Store.rowHeaderWidth-1.5, height:Store.columnHeaderHeight-1.5}); 118 | 119 | // //批注 120 | // luckysheetPostil.buildAllPs(Store.flowdata); 121 | 122 | $("#luckysheet_info_detail_input").val(luckysheetConfigsetting.title); 123 | } -------------------------------------------------------------------------------- /src/global/editor.js: -------------------------------------------------------------------------------- 1 | import browser from './browser'; 2 | import formula from './formula'; 3 | import { datagridgrowth } from './getdata'; 4 | import { jfrefreshgrid, jfrefreshgridall, jfrefreshrange } from './refresh'; 5 | import { getSheetIndex } from '../methods/get'; 6 | import Store from '../store'; 7 | 8 | const editor = { 9 | //worker+blob实现深拷贝替换extend 10 | deepCopyFlowDataState:false, 11 | deepCopyFlowDataCache:"", 12 | deepCopyFlowDataWorker:null, 13 | deepCopyFlowData:function(flowData){ 14 | let _this = this; 15 | 16 | if(_this.deepCopyFlowDataState){ 17 | if(_this.deepCopyFlowDataWorker != null){ 18 | _this.deepCopyFlowDataWorker.terminate(); 19 | } 20 | return _this.deepCopyFlowDataCache; 21 | } 22 | else{ 23 | if(flowData == null){ 24 | flowData = Store.flowdata; 25 | } 26 | 27 | return $.extend(true, [], flowData); 28 | } 29 | }, 30 | webWorkerFlowDataCache:function(flowData){ 31 | let _this = this; 32 | 33 | try{ 34 | if(_this.deepCopyFlowDataWorker != null){//存新的webwork前先销毁以前的 35 | _this.deepCopyFlowDataWorker.terminate(); 36 | } 37 | 38 | let funcTxt = 'data:text/javascript;chartset=US-ASCII,onmessage = function (e) { postMessage(e.data); };'; 39 | _this.deepCopyFlowDataState = false; 40 | 41 | //适配IE 42 | let worker; 43 | if(browser.isIE() == 1){ 44 | let response = "self.onmessage=function(e){postMessage(e.data);}"; 45 | worker = new Worker('./plugins/Worker-helper.js'); 46 | worker.postMessage(response); 47 | } 48 | else{ 49 | worker = new Worker(funcTxt); 50 | } 51 | 52 | _this.deepCopyFlowDataWorker = worker; 53 | worker.postMessage(flowData); 54 | worker.onmessage = function(e) { 55 | _this.deepCopyFlowDataCache = e.data; 56 | _this.deepCopyFlowDataState = true; 57 | }; 58 | } 59 | catch(e){ 60 | _this.deepCopyFlowDataCache = $.extend(true, [], flowData); 61 | } 62 | }, 63 | 64 | /** 65 | * @param {Array} dataChe 66 | * @param {Object} range 是否指定选区,默认为当前选区 67 | * @since Add range parameter. Update by siwei@2020-09-10. 68 | */ 69 | controlHandler: function (dataChe, range) { 70 | let _this = this; 71 | 72 | let d = _this.deepCopyFlowData(Store.flowdata);//取数据 73 | 74 | // let last = Store.luckysheet_select_save[Store.luckysheet_select_save.length - 1]; 75 | let last = range || Store.luckysheet_select_save[Store.luckysheet_select_save.length - 1]; 76 | let curR = last["row"] == null ? 0 : last["row"][0]; 77 | let curC = last["column"] == null ? 0 : last["column"][0]; 78 | let rlen = dataChe.length, clen = dataChe[0].length; 79 | 80 | let addr = curR + rlen - d.length, addc = curC + clen - d[0].length; 81 | if(addr > 0 || addc > 0){ 82 | d = datagridgrowth([].concat(d), addr, addc, true); 83 | } 84 | 85 | for (let r = 0; r < rlen; r++) { 86 | let x = [].concat(d[r + curR]); 87 | for (let c = 0; c < clen; c++) { 88 | let value = ""; 89 | if (dataChe[r] != null && dataChe[r][c] != null) { 90 | value = dataChe[r][c]; 91 | } 92 | x[c + curC] = value; 93 | } 94 | d[r + curR] = x; 95 | } 96 | 97 | if (addr > 0 || addc > 0) { 98 | jfrefreshgridall(d[0].length, d.length, d, null, Store.luckysheet_select_save, "datachangeAll"); 99 | } 100 | else { 101 | jfrefreshrange(d, Store.luckysheet_select_save); 102 | } 103 | }, 104 | clearRangeByindex: function (st_r, ed_r, st_c, ed_c, sheetIndex) { 105 | let index = getSheetIndex(sheetIndex); 106 | let d = $.extend(true, [], Store.luckysheetfile[index]["data"]); 107 | 108 | for (let r = st_r; r <= ed_r; r++) { 109 | let x = [].concat(d[r]); 110 | for (let c = st_c; c <= ed_c; c++) { 111 | formula.delFunctionGroup(r, c); 112 | formula.execFunctionGroup(r, c, ""); 113 | x[c] = null; 114 | } 115 | d[r] = x; 116 | } 117 | 118 | if(sheetIndex == Store.currentSheetIndex){ 119 | let rlen = ed_r - st_r + 1, 120 | clen = ed_c - st_c + 1; 121 | 122 | if (rlen > 5000) { 123 | jfrefreshgrid(d, [{ "row": [st_r, ed_r], "column": [st_c, ed_c] }]); 124 | } 125 | else { 126 | jfrefreshrange(d, { "row": [st_r, ed_r], "column": [st_c, ed_c] }); 127 | } 128 | } 129 | else{ 130 | Store.luckysheetfile[index]["data"] = d; 131 | } 132 | }, 133 | controlHandlerD: function (dataChe) { 134 | let _this = this; 135 | 136 | let d = _this.deepCopyFlowData(Store.flowdata);//取数据 137 | 138 | let last = Store.luckysheet_select_save[Store.luckysheet_select_save.length - 1]; 139 | let r1 = last["row"][0], r2 = last["row"][1]; 140 | let c1 = last["column"][0], c2 = last["column"][1]; 141 | let rlen = dataChe.length, clen = dataChe[0].length; 142 | 143 | let addr = r1 + rlen - d.length, addc = c1 + clen - d[0].length; 144 | if(addr >0 || addc > 0){ 145 | d = datagridgrowth([].concat(d), addr, addc, true); 146 | } 147 | 148 | for(let r = r1; r <= r2; r++){ 149 | for(let c = c1; c <= c2; c++){ 150 | d[r][c] = null; 151 | } 152 | } 153 | 154 | for(let i = 0; i < rlen; i++){ 155 | for(let j = 0; j < clen; j++){ 156 | d[r1 + i][c1 + j] = dataChe[i][j]; 157 | } 158 | } 159 | 160 | let range = [ 161 | { "row": [r1, r2], "column": [c1, c2] }, 162 | { "row": [r1, r1 + rlen - 1], "column": [c1, c1 + clen - 1] } 163 | ]; 164 | 165 | jfrefreshgrid(d, range); 166 | } 167 | }; 168 | 169 | export default editor; -------------------------------------------------------------------------------- /src/css/luckysheet-protection.css: -------------------------------------------------------------------------------- 1 | 2 | #luckysheet-modal-dialog-slider-protection .luckysheet-modal-dialog-slider-content{ 3 | background: #fff; 4 | } 5 | 6 | .luckysheet-slider-protection-config{ 7 | position: absolute; 8 | width: 100%; 9 | } 10 | 11 | .luckysheet-slider-protection-row{ 12 | position: relative; 13 | width: 98%; 14 | height: 35px; 15 | left: 1%; 16 | } 17 | 18 | .luckysheet-slider-protection-column{ 19 | position: absolute; 20 | height: 100%; 21 | } 22 | 23 | 24 | .luckysheet-slider-protection-config input, .luckysheet-slider-protection-config textarea, .luckysheet-protection-rangeItem-dialog input, .luckysheet-protection-rangeItem-dialog textarea, .luckysheet-protection-sheet-validation input{ 25 | border: 1px solid #d4d4d4; 26 | outline: none; 27 | } 28 | 29 | .luckysheet-slider-protection-config input:focus, .luckysheet-slider-protection-config textarea:focus, .luckysheet-protection-rangeItem-dialog input:focus, .luckysheet-protection-rangeItem-dialog textarea:focus,.luckysheet-protection-sheet-validation input:focus{ 30 | border: 1px solid #0389FB; 31 | outline: none; 32 | } 33 | 34 | .luckysheet-protection-input{ 35 | width: 100%; 36 | height: 19px; 37 | position: relative; 38 | } 39 | 40 | .luckysheet-protection-textarea{ 41 | width: 100%; 42 | height: 47px; 43 | position: relative; 44 | resize:none; 45 | } 46 | 47 | .luckysheet-protection-column-2x{ 48 | width: 20%; 49 | } 50 | 51 | .luckysheet-protection-column-3x{ 52 | width: 30%; 53 | } 54 | 55 | .luckysheet-protection-column-4x{ 56 | width: 40%; 57 | } 58 | 59 | .luckysheet-protection-column-5x{ 60 | width: 50%; 61 | } 62 | 63 | .luckysheet-protection-column-6x{ 64 | width: 60%; 65 | } 66 | 67 | .luckysheet-protection-column-7x{ 68 | width: 70%; 69 | } 70 | 71 | .luckysheet-protection-column-8x{ 72 | width: 80%; 73 | } 74 | 75 | .luckysheet-protection-column-9x{ 76 | width: 90%; 77 | } 78 | 79 | .luckysheet-protection-column-10x{ 80 | width: 100%; 81 | } 82 | 83 | .luckysheet-protection-column-left{ 84 | text-align: left; 85 | } 86 | 87 | .luckysheet-protection-column-center{ 88 | text-align: center; 89 | } 90 | 91 | .luckysheet-protection-column-right{ 92 | text-align: right; 93 | } 94 | 95 | .luckysheet-slider-protection-ok{ 96 | position: absolute; 97 | width: 100%; 98 | height: 100%; 99 | background: #0188fb; 100 | color: #fff; 101 | text-align: center; 102 | line-height: 45px; 103 | font-size: 16px; 104 | cursor: pointer; 105 | } 106 | 107 | .luckysheet-slider-protection-ok:hover{ 108 | background: #0181EE; 109 | } 110 | 111 | .luckysheet-slider-protection-ok:active{ 112 | background: #0074da; 113 | } 114 | 115 | .luckysheet-slider-protection-cancel{ 116 | position: absolute; 117 | width: 100%; 118 | height: 100%; 119 | background: #e6e6e6; 120 | color: #353535; 121 | text-align: center; 122 | line-height: 45px; 123 | font-size: 16px; 124 | cursor: pointer; 125 | } 126 | 127 | .luckysheet-slider-protection-cancel:hover{ 128 | background: #d6d6d6; 129 | } 130 | 131 | .luckysheet-slider-protection-cancel:active{ 132 | background: #c7c7c7; 133 | } 134 | 135 | .luckysheet-slider-protection-addRange{ 136 | line-height: 23px; 137 | font-size: 12px; 138 | top: 2px; 139 | height: 23px; 140 | } 141 | 142 | 143 | .luckysheet-protection-rangeItem{ 144 | position: relative; 145 | width: 100%; 146 | height: 30px; 147 | line-height: 30px; 148 | font-size: 12px; 149 | overflow: hidden; 150 | } 151 | 152 | .luckysheet-protection-rangeItem:hover{ 153 | background: #D5D5D5; 154 | } 155 | 156 | .luckysheet-protection-rangeItem > div{ 157 | position: absolute; 158 | height: 100%; 159 | text-align: center; 160 | overflow: hidden; 161 | } 162 | 163 | .luckysheet-protection-rangeItem .luckysheet-protection-rangeItem-del{ 164 | left: 5px; 165 | top:5px; 166 | height: 20px; 167 | width: 20px; 168 | font-size: 14px; 169 | line-height: 20px; 170 | cursor: pointer; 171 | } 172 | 173 | .luckysheet-protection-rangeItem .luckysheet-protection-rangeItem-name{ 174 | left: 30px; 175 | width: 80px; 176 | text-align: left; 177 | } 178 | 179 | .luckysheet-protection-rangeItem .luckysheet-protection-rangeItem-range{ 180 | left: 110px; 181 | width: 120px; 182 | } 183 | 184 | .luckysheet-protection-rangeItem .luckysheet-protection-rangeItem-update{ 185 | left: 230px; 186 | width: 30px; 187 | font-size: 14px; 188 | top: 5px; 189 | height: 20px; 190 | width: 20px; 191 | line-height: 20px; 192 | cursor: pointer; 193 | } 194 | 195 | .luckysheet-protection-rangeItem .luckysheet-protection-rangeItem-del:hover, .luckysheet-protection-rangeItem .luckysheet-protection-rangeItem-update:hover{ 196 | background: #0181EE; 197 | color: #fff; 198 | } 199 | 200 | .luckysheet-protection-rangeItem .luckysheet-protection-rangeItem-del:active, .luckysheet-protection-rangeItem .luckysheet-protection-rangeItem-update:active{ 201 | background: #0074da; 202 | color: #fff; 203 | } 204 | 205 | 206 | .luckysheet-protection-rangeItem-content{ 207 | position: relative; 208 | width: 350px; 209 | height: 270px; 210 | } 211 | 212 | 213 | #luckysheet-protection-rangeItem-dialog .luckysheet-slider-protection-column .range { 214 | width: 100%; 215 | height: 30px; 216 | border: 1px solid #d4d4d4; 217 | } 218 | 219 | #luckysheet-protection-rangeItem-dialog .luckysheet-slider-protection-column .range input { 220 | width: calc(100% - 30px); 221 | height: 30px; 222 | padding: 0 10px; 223 | float: left; 224 | border: none; 225 | outline-style: none; 226 | box-sizing: border-box; 227 | } 228 | 229 | #luckysheet-protection-rangeItem-dialog .luckysheet-slider-protection-column .range i.fa-table { 230 | float: right; 231 | margin-top: 9px; 232 | margin-right: 5px; 233 | cursor: pointer; 234 | color: #6598F3; 235 | } 236 | 237 | .luckysheet-protection-rangeItemTextarea{ 238 | width: 100%; 239 | height: 120px; 240 | position: relative; 241 | resize:none; 242 | } 243 | 244 | .luckysheet-protection-rangeItemiInput{ 245 | width: 100%; 246 | height: 23px; 247 | position: relative; 248 | } 249 | 250 | 251 | .luckysheet-protection-sheet-validation{ 252 | width: 390px; 253 | height: 180px; 254 | display: none; 255 | } -------------------------------------------------------------------------------- /src/global/validate.js: -------------------------------------------------------------------------------- 1 | import luckysheetConfigsetting from '../controllers/luckysheetConfigsetting'; 2 | import Store from '../store'; 3 | 4 | export const error = { 5 | v: "#VALUE!", //错误的参数或运算符 6 | n: "#NAME?", //公式名称错误 7 | na: "#N/A", //函数或公式中没有可用数值 8 | r: "#REF!", //删除了由其他公式引用的单元格 9 | d: "#DIV/0!", //除数是0或空单元格 10 | nm: "#NUM!", //当公式或函数中某个数字有问题时 11 | nl: "#NULL!", //交叉运算符(空格)使用不正确 12 | sp: "#SPILL!" //数组范围有其它值 13 | } 14 | 15 | //是否是空值 16 | function isRealNull(val) { 17 | if(val == null || val.toString().replace(/\s/g, "") == ""){ 18 | return true; 19 | } 20 | else{ 21 | return false; 22 | } 23 | } 24 | 25 | //是否是纯数字 26 | function isRealNum(val) { 27 | if(val == null || val.toString().replace(/\s/g, "") === ""){ 28 | return false; 29 | } 30 | 31 | if(typeof val == "boolean"){ 32 | return false; 33 | } 34 | 35 | if(!isNaN(val)){ 36 | return true; 37 | } 38 | else{ 39 | return false; 40 | } 41 | } 42 | 43 | //是否是错误类型 44 | function valueIsError(value) { 45 | let isError = false; 46 | 47 | for(let x in error){ 48 | if(value == error[x]){ 49 | isError = true; 50 | break; 51 | } 52 | } 53 | 54 | return isError; 55 | } 56 | 57 | //是否有中文 58 | function hasChinaword(s) { 59 | let patrn = /[\u4E00-\u9FA5]|[\uFE30-\uFFA0]/gi; 60 | 61 | if (!patrn.exec(s)) { 62 | return false; 63 | } 64 | else { 65 | return true; 66 | } 67 | } 68 | 69 | //是否为非编辑模式 70 | function isEditMode() { 71 | if(luckysheetConfigsetting.editMode){ 72 | return true; 73 | } 74 | else{ 75 | return false; 76 | } 77 | } 78 | 79 | /** 80 | * @description: 检查是否允许前台进行表格编辑 81 | * @param {*} 82 | * @return {Boolean} true:允许编辑 fasle:不允许 83 | */ 84 | function checkIsAllowEdit(){ 85 | if (Store.allowEdit) { 86 | return true; 87 | } 88 | else { 89 | return false; 90 | } 91 | } 92 | 93 | //范围是否只包含部分合并单元格 94 | function hasPartMC(cfg, r1, r2, c1, c2) { 95 | let hasPartMC = false; 96 | 97 | for(let x in Store.config["merge"]){ 98 | let mc = cfg["merge"][x]; 99 | 100 | if(r1 < mc.r){ 101 | if(r2 >= mc.r && r2 < (mc.r + mc.rs - 1)){ 102 | if(c1 >= mc.c && c1 <= (mc.c + mc.cs - 1)){ 103 | hasPartMC = true; 104 | break; 105 | } 106 | else if(c2 >= mc.c && c2 <= (mc.c + mc.cs - 1)){ 107 | hasPartMC = true; 108 | break; 109 | } 110 | else if(c1 < mc.c && c2 > (mc.c + mc.cs - 1)){ 111 | hasPartMC = true; 112 | break; 113 | } 114 | } 115 | else if(r2 >= mc.r && r2 == (mc.r + mc.rs - 1)){ 116 | if(c1 > mc.c && c1 < (mc.c + mc.cs - 1)){ 117 | hasPartMC = true; 118 | break; 119 | } 120 | else if(c2 > mc.c && c2 < (mc.c + mc.cs - 1)){ 121 | hasPartMC = true; 122 | break; 123 | } 124 | else if(c1 == mc.c && c2 < (mc.c + mc.cs - 1)){ 125 | hasPartMC = true; 126 | break; 127 | } 128 | else if(c1 > mc.c && c2 == (mc.c + mc.cs - 1)){ 129 | hasPartMC = true; 130 | break; 131 | } 132 | } 133 | else if(r2 > (mc.r + mc.rs - 1)){ 134 | if(c1 > mc.c && c1 <= (mc.c + mc.cs - 1)){ 135 | hasPartMC = true; 136 | break; 137 | } 138 | else if(c2 >= mc.c && c2 < (mc.c + mc.cs - 1)){ 139 | hasPartMC = true; 140 | break; 141 | } 142 | else if(c1 == mc.c && c2 < (mc.c + mc.cs - 1)){ 143 | hasPartMC = true; 144 | break; 145 | } 146 | else if(c1 > mc.c && c2 == (mc.c + mc.cs - 1)){ 147 | hasPartMC = true; 148 | break; 149 | } 150 | } 151 | } 152 | else if(r1 == mc.r){ 153 | if(r2 < (mc.r + mc.rs - 1)){ 154 | if(c1 >= mc.c && c1 <= (mc.c + mc.cs - 1)){ 155 | hasPartMC = true; 156 | break; 157 | } 158 | else if(c2 >= mc.c && c2 <= (mc.c + mc.cs - 1)){ 159 | hasPartMC = true; 160 | break; 161 | } 162 | else if(c1 < mc.c && c2 > (mc.c + mc.cs - 1)){ 163 | hasPartMC = true; 164 | break; 165 | } 166 | } 167 | else if(r2 >= (mc.r + mc.rs - 1)){ 168 | if(c1 > mc.c && c1 <= (mc.c + mc.cs - 1)){ 169 | hasPartMC = true; 170 | break; 171 | } 172 | else if(c2 >= mc.c && c2 < (mc.c + mc.cs - 1)){ 173 | hasPartMC = true; 174 | break; 175 | } 176 | else if(c1 == mc.c && c2 < (mc.c + mc.cs - 1)){ 177 | hasPartMC = true; 178 | break; 179 | } 180 | else if(c1 > mc.c && c2 == (mc.c + mc.cs - 1)){ 181 | hasPartMC = true; 182 | break; 183 | } 184 | } 185 | } 186 | else if(r1 <= (mc.r + mc.rs - 1)){ 187 | if(c1 >= mc.c && c1 <= (mc.c + mc.cs - 1)){ 188 | hasPartMC = true; 189 | break; 190 | } 191 | else if(c2 >= mc.c && c2 <= (mc.c + mc.cs - 1)){ 192 | hasPartMC = true; 193 | break; 194 | } 195 | else if(c1 < mc.c && c2 > (mc.c + mc.cs - 1)){ 196 | hasPartMC = true; 197 | break; 198 | } 199 | } 200 | } 201 | 202 | return hasPartMC; 203 | } 204 | 205 | //获取单个字符的字节数 206 | function checkWordByteLength(value) { 207 | return Math.ceil(value.charCodeAt().toString(2).length / 8); 208 | } 209 | 210 | 211 | export { 212 | isRealNull, 213 | isRealNum, 214 | valueIsError, 215 | hasChinaword, 216 | isEditMode, 217 | checkIsAllowEdit, 218 | hasPartMC, 219 | checkWordByteLength 220 | } -------------------------------------------------------------------------------- /src/controllers/cellFormat.js: -------------------------------------------------------------------------------- 1 | import Store from '../store'; 2 | import { replaceHtml,transformRangeToAbsolute,openSelfModel } from '../utils/util'; 3 | import { modelHTML } from './constant'; 4 | import sheetmanage from './sheetmanage'; 5 | import menuButton from './menuButton'; 6 | import {checkProtectionNotEnable} from './protection'; 7 | import { jfrefreshgrid } from '../global/refresh'; 8 | import locale from '../locale/locale'; 9 | import { setcellvalue } from '../global/setdata'; 10 | 11 | 12 | let isInitialCellFormatModel = false; 13 | 14 | function initialCellFormatModelEvent(){ 15 | const _locale = locale(); 16 | const local_cellFormat = _locale.cellFormat; 17 | 18 | $("#luckysheet-cellFormat-confirm").click(function(){ 19 | let locked = $("#luckysheet-protection-check-locked").is(':checked'); 20 | let hidden = $("#luckysheet-protection-check-hidden").is(':checked'); 21 | 22 | locked = locked==true?1:0; 23 | hidden = hidden==true?1:0; 24 | 25 | let d = recycleSeletion( 26 | function(cell, r, c, data){ 27 | if(cell==null){ 28 | setcellvalue(r, c, data, { 29 | lo:locked, 30 | hi:hidden 31 | }); 32 | } 33 | else{ 34 | cell.lo = locked; 35 | cell.hi = hidden; 36 | } 37 | }, 38 | function(){ 39 | alert(local_cellFormat.sheetDataIsNullAlert); 40 | } 41 | ); 42 | 43 | jfrefreshgrid(d, undefined, undefined, false); 44 | 45 | $("#luckysheet-cellFormat-config").hide(); 46 | $("#luckysheet-modal-dialog-mask").hide(); 47 | }); 48 | } 49 | 50 | function recycleSeletion(cycleFunction, dataIsNullFunction){ 51 | if(Store.luckysheet_select_save != null && Store.luckysheet_select_save.length > 0){ 52 | let sheetFile = sheetmanage.getSheetByIndex(), data=sheetFile.data; 53 | if(data!=null){ 54 | 55 | for(let i=0;i