commit
b30a01cfbe
32 changed files with 1816 additions and 0 deletions
@ -0,0 +1,9 @@ |
|||
root = true |
|||
|
|||
[*] |
|||
charset = utf-8 |
|||
indent_style = space |
|||
indent_size = 2 |
|||
end_of_line = lf |
|||
insert_final_newline = true |
|||
trim_trailing_whitespace = true |
@ -0,0 +1,5 @@ |
|||
node_modules |
|||
dist |
|||
out |
|||
*.log* |
|||
package-lock.json |
@ -0,0 +1 @@ |
|||
ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/ |
@ -0,0 +1,6 @@ |
|||
out |
|||
dist |
|||
pnpm-lock.yaml |
|||
LICENSE.md |
|||
tsconfig.json |
|||
tsconfig.*.json |
@ -0,0 +1,6 @@ |
|||
{ |
|||
"singleQuote": true, |
|||
"semi": false, |
|||
"printWidth": 100, |
|||
"trailingComma": "none" |
|||
} |
@ -0,0 +1,34 @@ |
|||
# my-app |
|||
|
|||
An Electron application with Vue |
|||
|
|||
## Recommended IDE Setup |
|||
|
|||
- [VSCode](https://code.visualstudio.com/) + [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) + [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) |
|||
|
|||
## Project Setup |
|||
|
|||
### Install(node 16.20.1) |
|||
|
|||
```bash |
|||
$ npm install |
|||
``` |
|||
|
|||
### Development |
|||
|
|||
```bash |
|||
$ npm run dev |
|||
``` |
|||
|
|||
### Build |
|||
|
|||
```bash |
|||
# For windows |
|||
$ npm run build:win |
|||
|
|||
# For macOS |
|||
$ npm run build:mac |
|||
|
|||
# For Linux |
|||
$ npm run build:linux |
|||
``` |
@ -0,0 +1,12 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
|||
<plist version="1.0"> |
|||
<dict> |
|||
<key>com.apple.security.cs.allow-jit</key> |
|||
<true/> |
|||
<key>com.apple.security.cs.allow-unsigned-executable-memory</key> |
|||
<true/> |
|||
<key>com.apple.security.cs.allow-dyld-environment-variables</key> |
|||
<true/> |
|||
</dict> |
|||
</plist> |
Binary file not shown.
After Width: | Height: | Size: 35 KiB |
@ -0,0 +1,36 @@ |
|||
const { notarize } = require('electron-notarize') |
|||
|
|||
module.exports = async (context) => { |
|||
if (process.platform !== 'darwin') return |
|||
|
|||
console.log('aftersign hook triggered, start to notarize app.') |
|||
|
|||
if (!process.env.CI) { |
|||
console.log(`skipping notarizing, not in CI.`) |
|||
return |
|||
} |
|||
|
|||
if (!('APPLE_ID' in process.env && 'APPLE_ID_PASS' in process.env)) { |
|||
console.warn('skipping notarizing, APPLE_ID and APPLE_ID_PASS env variables must be set.') |
|||
return |
|||
} |
|||
|
|||
const appId = 'com.electron.app' |
|||
|
|||
const { appOutDir } = context |
|||
|
|||
const appName = context.packager.appInfo.productFilename |
|||
|
|||
try { |
|||
await notarize({ |
|||
appBundleId: appId, |
|||
appPath: `${appOutDir}/${appName}.app`, |
|||
appleId: process.env.APPLE_ID, |
|||
appleIdPassword: process.env.APPLEIDPASS |
|||
}) |
|||
} catch (error) { |
|||
console.error(error) |
|||
} |
|||
|
|||
console.log(`done notarizing ${appId}.`) |
|||
} |
@ -0,0 +1,43 @@ |
|||
appId: com.electron.lichong |
|||
productName: 销售统计 |
|||
directories: |
|||
buildResources: build |
|||
files: |
|||
- '!**/.vscode/*' |
|||
- '!src/*' |
|||
- '!electron.vite.config.{js,ts,mjs,cjs}' |
|||
- '!{.eslintignore,.eslintrc.cjs,.prettierignore,.prettierrc.yaml,dev-app-update.yml,CHANGELOG.md,README.md}' |
|||
asarUnpack: |
|||
- '**/*.{node,dll}' |
|||
afterSign: build/notarize.js |
|||
win: |
|||
executableName: lichong-app |
|||
nsis: |
|||
oneClick: false |
|||
artifactName: 销售统计-${version}-setup.${ext} |
|||
allowToChangeInstallationDirectory: true |
|||
shortcutName: ${productName} |
|||
uninstallDisplayName: ${productName} |
|||
createDesktopShortcut: always |
|||
mac: |
|||
entitlementsInherit: build/entitlements.mac.plist |
|||
extendInfo: |
|||
- NSCameraUsageDescription: Application requests access to the device's camera. |
|||
- NSMicrophoneUsageDescription: Application requests access to the device's microphone. |
|||
- NSDocumentsFolderUsageDescription: Application requests access to the user's Documents folder. |
|||
- NSDownloadsFolderUsageDescription: Application requests access to the user's Downloads folder. |
|||
dmg: |
|||
artifactName: ${name}-${version}.${ext} |
|||
linux: |
|||
target: |
|||
- AppImage |
|||
- snap |
|||
- deb |
|||
maintainer: electronjs.org |
|||
category: Utility |
|||
appImage: |
|||
artifactName: ${name}-${version}.${ext} |
|||
npmRebuild: false |
|||
publish: |
|||
provider: generic |
|||
url: https://example.com/auto-updates |
@ -0,0 +1,28 @@ |
|||
import { resolve } from 'path' |
|||
import { defineConfig, externalizeDepsPlugin } from 'electron-vite' |
|||
import vue from '@vitejs/plugin-vue' |
|||
|
|||
export default defineConfig({ |
|||
main: { |
|||
plugins: [externalizeDepsPlugin()] |
|||
}, |
|||
preload: { |
|||
plugins: [externalizeDepsPlugin()], |
|||
build: { |
|||
rollupOptions: { |
|||
input: { |
|||
dl: resolve(__dirname, 'src/preload/dl.js'), |
|||
index: resolve(__dirname, 'src/preload/index.js') |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
renderer: { |
|||
resolve: { |
|||
alias: { |
|||
'@renderer': resolve('src/renderer/src') |
|||
} |
|||
}, |
|||
plugins: [vue()] |
|||
} |
|||
}) |
@ -0,0 +1,6 @@ |
|||
{ |
|||
"exclude": [ |
|||
"node_modules", |
|||
"public" |
|||
] |
|||
} |
@ -0,0 +1,41 @@ |
|||
{ |
|||
"name": "electron", |
|||
"version": "1.0.1", |
|||
"description": "An Electron application with Vue", |
|||
"main": "./out/main/index.js", |
|||
"author": "lichong", |
|||
"homepage": "https://www.electronjs.org", |
|||
"scripts": { |
|||
"npmi": "npm i", |
|||
"dev": "electron-vite dev", |
|||
"build": "electron-vite build", |
|||
"build:win": "npm run build && electron-builder --win --config", |
|||
"build:mac": "npm run build && electron-builder --mac --config", |
|||
"build:linux": "npm run build && electron-builder --linux --config" |
|||
}, |
|||
"dependencies": { |
|||
"@electron-toolkit/preload": "^1.0.2", |
|||
"@electron-toolkit/utils": "^1.0.2", |
|||
"dayjs": "^1.11.11", |
|||
"dexie": "^4.0.8", |
|||
"echarts": "^5.5.1", |
|||
"element-plus": "^2.7.1", |
|||
"lodash": "^4.17.21", |
|||
"vxe-pc-ui": "^4.2.26", |
|||
"vxe-table": "^4.7.94", |
|||
"xlsx": "^0.18.5" |
|||
}, |
|||
"devDependencies": { |
|||
"@rushstack/eslint-patch": "^1.2.0", |
|||
"@vitejs/plugin-vue": "^3.1.2", |
|||
"@vue/eslint-config-prettier": "^7.0.0", |
|||
"electron": "^20.3.2", |
|||
"electron-builder": "^23.6.0", |
|||
"electron-notarize": "^1.2.1", |
|||
"electron-vite": "^1.0.11", |
|||
"less": "^4.1.3", |
|||
"prettier": "^2.7.1", |
|||
"vite": "^3.1.8", |
|||
"vue": "^3.2.41" |
|||
} |
|||
} |
After Width: | Height: | Size: 16 KiB |
@ -0,0 +1,70 @@ |
|||
import { app, shell, BrowserWindow, nativeImage } from 'electron' |
|||
import * as path from 'path' |
|||
import { electronApp, optimizer, is } from '@electron-toolkit/utils' |
|||
|
|||
// logo
|
|||
const logoIcon = nativeImage.createFromPath(path.join(__dirname, '../../public/icon/icon.jpg')) |
|||
// 主窗口
|
|||
let mainWindow |
|||
function createWindow() { |
|||
mainWindow = new BrowserWindow({ |
|||
minWidth: 1366, |
|||
minHeight: 900, |
|||
height: 1260, |
|||
show: false, |
|||
autoHideMenuBar: true, |
|||
icon: logoIcon, |
|||
webPreferences: { |
|||
preload: path.resolve(__dirname, '../preload/index.js'), |
|||
sandbox: false, |
|||
nodeIntegration: true, |
|||
contextIsolation: false |
|||
} |
|||
}) |
|||
mainWindow.on('ready-to-show', () => { |
|||
mainWindow.show() |
|||
// mainWindow.webContents.openDevTools()
|
|||
}) |
|||
mainWindow.webContents.setWindowOpenHandler((details) => { |
|||
shell.openExternal(details.url) |
|||
return { action: 'deny' } |
|||
}) |
|||
// mainWindow.loadURL('http://localhost:5173/')
|
|||
|
|||
mainWindow.on('close', () => { |
|||
app.exit() |
|||
}) |
|||
if (is.dev && process.env['ELECTRON_RENDERER_URL']) { |
|||
mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL']) |
|||
} else { |
|||
mainWindow.loadFile(path.join(__dirname, '../renderer/index.html')) |
|||
} |
|||
} |
|||
app.whenReady().then(() => { |
|||
electronApp.setAppUserModelId('com.electron') |
|||
// Default open or close DevTools by F12 in development
|
|||
// and ignore CommandOrControl + R in production.
|
|||
// see https://github.com/alex8088/electron-toolkit/tree/master/packages/utils
|
|||
app.on('browser-window-created', (_, window) => { |
|||
optimizer.watchWindowShortcuts(window) |
|||
}) |
|||
|
|||
createWindow() |
|||
|
|||
app.on('activate', function () { |
|||
// On macOS it's common to re-create a window in the app when the
|
|||
// dock icon is clicked and there are no other windows open.
|
|||
if (BrowserWindow.getAllWindows().length === 0) createWindow() |
|||
}) |
|||
}) |
|||
app.commandLine.appendSwitch('autoplay-policy', 'no-user-gesture-required'); |
|||
// Quit when all windows are closed, except on macOS. There, it's common
|
|||
// for applications and their menu bar to stay active until the user quits
|
|||
// explicitly with Cmd + Q.
|
|||
app.on('window-all-closed', () => { |
|||
if (process.platform !== 'darwin') { |
|||
app.quit() |
|||
} |
|||
}) |
|||
// In this file you can include the rest of your app"s specific main process
|
|||
// code. You can also put them in separate files and require them here.
|
@ -0,0 +1,21 @@ |
|||
import { contextBridge, clipboard } from 'electron' |
|||
import { electronAPI } from '@electron-toolkit/preload' |
|||
|
|||
// Custom APIs for renderer
|
|||
const api = {} |
|||
|
|||
// Use `contextBridge` APIs to expose Electron APIs to
|
|||
// renderer only if context isolation is enabled, otherwise
|
|||
// just add to the DOM global.
|
|||
if (process.contextIsolated) { |
|||
try { |
|||
contextBridge.exposeInMainWorld('electron', electronAPI) |
|||
contextBridge.exposeInMainWorld('api', api) |
|||
contextBridge.exposeInMainWorld('elecClipboard', clipboard) |
|||
} catch (error) { |
|||
console.error(error) |
|||
} |
|||
} else { |
|||
window.electron = electronAPI |
|||
window.api = api |
|||
} |
@ -0,0 +1,21 @@ |
|||
import { contextBridge, clipboard } from 'electron' |
|||
import { electronAPI } from '@electron-toolkit/preload' |
|||
|
|||
// Custom APIs for renderer
|
|||
const api = {} |
|||
|
|||
// Use `contextBridge` APIs to expose Electron APIs to
|
|||
// renderer only if context isolation is enabled, otherwise
|
|||
// just add to the DOM global.
|
|||
if (process.contextIsolated) { |
|||
try { |
|||
contextBridge.exposeInMainWorld('electron', electronAPI) |
|||
contextBridge.exposeInMainWorld('api', api) |
|||
contextBridge.exposeInMainWorld('elecClipboard', clipboard) |
|||
} catch (error) { |
|||
console.error(error) |
|||
} |
|||
} else { |
|||
window.electron = electronAPI |
|||
window.api = api |
|||
} |
@ -0,0 +1,16 @@ |
|||
<!DOCTYPE html> |
|||
<html> |
|||
|
|||
<head> |
|||
<meta charset="UTF-8" /> |
|||
<title>销售统计</title> |
|||
<meta http-equiv="Content-Security-Policy" |
|||
content="default-src 'self'; script-src 'self';img-src 'self' data:; style-src 'self' 'unsafe-inline';font-src 'self' data:;" /> |
|||
</head> |
|||
|
|||
<body> |
|||
<div id="app"></div> |
|||
<script type="module" src="/src/main.js"></script> |
|||
</body> |
|||
|
|||
</html> |
@ -0,0 +1,90 @@ |
|||
<template> |
|||
<div class="appClass" v-if="isVip"> |
|||
<el-tabs tab-position="left" class="appClassLeft" v-model="tabName"> |
|||
<el-tab-pane label="销售统计" name="sale1"> |
|||
<dashboardComponent></dashboardComponent> |
|||
</el-tab-pane> |
|||
<el-tab-pane label="供应商管理" name="sale6"> |
|||
<saleComponent></saleComponent> |
|||
</el-tab-pane> |
|||
<el-tab-pane label="天猫小店泰和苑店" name="sale2"> |
|||
<saleComponent></saleComponent> |
|||
</el-tab-pane> |
|||
<el-tab-pane label="天猫小店牡丹园店" name="sale3"> |
|||
<saleComponent></saleComponent> |
|||
</el-tab-pane> |
|||
<el-tab-pane label="金叶便利东方红店" name="sale4"> |
|||
<saleComponent></saleComponent> |
|||
</el-tab-pane> |
|||
<el-tab-pane label="天恩认养一头牛官旗店" name="sale5"> |
|||
<saleComponent></saleComponent> |
|||
</el-tab-pane> |
|||
</el-tabs> |
|||
</div> |
|||
<div v-else class="noVip"> |
|||
<h3> |
|||
<el-tag type="danger" class="noVipTag">体验已过期,请联系管理员。</el-tag> |
|||
</h3> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import _ from 'lodash' |
|||
import dayjs from 'dayjs' |
|||
import saleComponent from "./views/sale.vue" |
|||
import dashboardComponent from "./views/dashboard.vue" |
|||
|
|||
export default { |
|||
name: 'app', |
|||
components: { saleComponent, dashboardComponent }, |
|||
data() { |
|||
return { |
|||
_: _, |
|||
dayjs: dayjs, |
|||
isVip: false, |
|||
jihuoma: "", |
|||
zhucema: "", |
|||
tabName: "sale1" |
|||
} |
|||
}, |
|||
watch: {}, |
|||
computed: {}, |
|||
methods: { |
|||
}, |
|||
async mounted() { |
|||
let fiveDay = dayjs('2025-01-15T00:00:00').valueOf() |
|||
if (!this.isVip) { |
|||
if (dayjs().valueOf() > fiveDay) { |
|||
this.isVip = false |
|||
return |
|||
} else { |
|||
this.isVip = true |
|||
} |
|||
} |
|||
}, |
|||
|
|||
} |
|||
</script> |
|||
<style scoped> |
|||
.appClass { |
|||
width: 100vw; |
|||
height: 100vh; |
|||
position: relative; |
|||
} |
|||
|
|||
.appClassLeft { |
|||
height: 100vh; |
|||
position: relative; |
|||
} |
|||
|
|||
.noVip { |
|||
text-align: center; |
|||
margin-top: calc(50vh - 98px); |
|||
} |
|||
|
|||
.noVipTag { |
|||
font-size: 48px; |
|||
line-height: 48px; |
|||
padding: 36px; |
|||
} |
|||
</style> |
@ -0,0 +1,9 @@ |
|||
html, |
|||
body { |
|||
margin: 0; |
|||
padding: 0; |
|||
} |
|||
|
|||
.is-left { |
|||
background-color: #999 !important; |
|||
} |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,8 @@ |
|||
import Dexie from 'dexie'; |
|||
|
|||
export const myDatabase = new Dexie('myDatabase'); |
|||
|
|||
// 定义数据库版本
|
|||
myDatabase.version(2).stores({ |
|||
sale: '++id, 货号, tableName, 品名, 供应商名称, 销售数量, 销售金额, 退货数量, 退货金额, 数量小计, 金额小计, create_at, update_at', |
|||
}); |
@ -0,0 +1,97 @@ |
|||
[ |
|||
{ |
|||
"label": "货号", |
|||
"prop": "货号", |
|||
"type": "text", |
|||
"width": "130", |
|||
"isSort": true, |
|||
"tableShow": true, |
|||
"formShow": true |
|||
}, |
|||
{ |
|||
"label": "品名", |
|||
"prop": "品名", |
|||
"type": "text", |
|||
"isSort": true, |
|||
"tableShow": true, |
|||
"formShow": true |
|||
}, |
|||
{ |
|||
"label": "供应商名称", |
|||
"prop": "供应商名称", |
|||
"type": "text", |
|||
"isSort": true, |
|||
"tableShow": true, |
|||
"formShow": true |
|||
}, |
|||
{ |
|||
"label": "销售数量", |
|||
"prop": "销售数量", |
|||
"type": "text", |
|||
"width": "100", |
|||
"isSort": true, |
|||
"tableShow": true, |
|||
"formShow": true |
|||
}, |
|||
{ |
|||
"label": "销售金额", |
|||
"prop": "销售金额", |
|||
"type": "text", |
|||
"width": "100", |
|||
"isSort": true, |
|||
"tableShow": true, |
|||
"formShow": true |
|||
}, |
|||
{ |
|||
"label": "退货数量", |
|||
"prop": "退货数量", |
|||
"type": "text", |
|||
"width": "100", |
|||
"isSort": true, |
|||
"tableShow": true, |
|||
"formShow": true |
|||
}, |
|||
{ |
|||
"label": "退货金额", |
|||
"prop": "退货金额", |
|||
"type": "text", |
|||
"width": "100", |
|||
"isSort": true, |
|||
"tableShow": true, |
|||
"formShow": true |
|||
}, |
|||
{ |
|||
"label": "数量小计", |
|||
"prop": "数量小计", |
|||
"type": "text", |
|||
"width": "100", |
|||
"isSort": true, |
|||
"tableShow": true, |
|||
"formShow": true |
|||
}, |
|||
{ |
|||
"label": "金额小计", |
|||
"prop": "金额小计", |
|||
"type": "text", |
|||
"width": "100", |
|||
"isSort": true, |
|||
"tableShow": true, |
|||
"formShow": true |
|||
}, |
|||
{ |
|||
"label": "创建时间", |
|||
"prop": "create_at", |
|||
"type": "date", |
|||
"isSort": false, |
|||
"tableShow": false, |
|||
"formShow": false |
|||
}, |
|||
{ |
|||
"label": "更新时间", |
|||
"prop": "update_at", |
|||
"type": "date", |
|||
"isSort": false, |
|||
"tableShow": false, |
|||
"formShow": false |
|||
} |
|||
] |
@ -0,0 +1,68 @@ |
|||
<template> |
|||
<div class="tableClass"> |
|||
<el-form :model="formData" label-suffix=":" :disabled="disabled" @submit.prevent> |
|||
<el-row> |
|||
<el-col :key="formIndex" v-for="(formItem, formIndex) in formHeader" :span="4"> |
|||
<el-form-item :label="formItem.label"> |
|||
<template v-if="['text', 'textarea'].includes(formItem.type)"> |
|||
<el-input v-model="formData[formItem.prop]" :type="formItem.type" :autosize="{ minRows: 3, maxRows: 15 }" |
|||
style="width: 95%;" /> |
|||
</template> |
|||
<template v-else-if="formItem.type === 'date'"> |
|||
<el-date-picker v-model="formData[formItem.prop]" type="date" |
|||
:disabled="['create_at', 'update_at'].indexOf(formItem.prop) !== -1" |
|||
:placeholder="`请选择${formItem.label}`" /> |
|||
</template> |
|||
</el-form-item> |
|||
</el-col> |
|||
</el-row> |
|||
</el-form> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import _, { max, min } from 'lodash' |
|||
import dayjs from 'dayjs' |
|||
import { ElMessage, ElMessageBox } from 'element-plus' |
|||
export default { |
|||
name: 'formcomponent', |
|||
components: {}, |
|||
props: { |
|||
formHeader: { |
|||
type: Array, |
|||
default: () => { |
|||
return [] |
|||
} |
|||
}, |
|||
formData: { |
|||
type: Object, |
|||
default: () => { |
|||
return {} |
|||
} |
|||
}, |
|||
disabled: { |
|||
type: Boolean, |
|||
default: () => { |
|||
return false |
|||
} |
|||
} |
|||
|
|||
}, |
|||
data() { |
|||
return { |
|||
_: _, |
|||
dayjs: dayjs, |
|||
} |
|||
}, |
|||
methods: {}, |
|||
async mounted() { }, |
|||
watch: {}, |
|||
computed: {} |
|||
} |
|||
</script> |
|||
<style scoped> |
|||
.tableClass { |
|||
text-align: left; |
|||
text-align-last: left; |
|||
} |
|||
</style> |
@ -0,0 +1,142 @@ |
|||
<template> |
|||
<div class="tableClass"> |
|||
<el-auto-resizer> |
|||
<template #default="{ height, width }"> |
|||
<vxe-table show-overflow :data="tableData" round :height="height" :scroll-y="{ enabled: true, gt: 0 }" |
|||
:checkbox-config="{ labelField: 'seq', highlight: true }" @checkbox-all="selectAllChangeEvent" |
|||
@checkbox-change="selectChange" ref="tableRef" border min-height="806px" :column-config="{ resizable: true }" |
|||
:footer-data="footerData" :show-footer="true"> |
|||
<vxe-column type="checkbox" width="60"></vxe-column> |
|||
<vxe-column :field="headerItem.prop" :title="headerItem.label" |
|||
v-for="(headerItem, headerIndex) in tableHeader" :key="headerIndex" :sortable="headerItem.isSort" |
|||
:width="headerItem.width"> |
|||
<template #default="{ row }"> |
|||
<template v-if="headerItem.type === 'text'"> |
|||
<span>{{ row[headerItem.prop] }}</span> |
|||
</template> |
|||
<template v-else-if="headerItem.type === 'date'"> |
|||
<span>{{ dayjs(row[headerItem.prop]).format("YYYY-MM-DD HH:mm:ss") }}</span> |
|||
</template> |
|||
<template v-else>{{ row[headerItem.prop] }}</template> |
|||
</template> |
|||
</vxe-column> |
|||
<vxe-column title="操作" width="145"> |
|||
<template #default="{ row }"> |
|||
<el-button type="primary" circle @click="optClick(row, 'edit')"> |
|||
<el-icon> |
|||
<Edit /> |
|||
</el-icon> |
|||
</el-button> |
|||
<el-button type="info" circle @click="optClick(row, 'info')"> |
|||
<el-icon> |
|||
<InfoFilled /> |
|||
</el-icon> |
|||
</el-button> |
|||
<el-button type="danger" circle @click="optClick(row, 'del')"> |
|||
<el-icon> |
|||
<Delete /> |
|||
</el-icon> |
|||
</el-button> |
|||
</template> |
|||
</vxe-column> |
|||
</vxe-table> |
|||
<vxe-pager :current-page.sync="pageVO.currentPage" :page-size.sync="pageVO.pageSize" :total="pageVO.total" |
|||
@page-change="pageChange" :page-sizes="pageVO.pageSizes" |
|||
:layouts="['Home', 'PrevJump', 'PrevPage', 'Number', 'NextPage', 'NextJump', 'End', 'Sizes', 'FullJump', 'Total']"> |
|||
</vxe-pager> |
|||
</template> |
|||
</el-auto-resizer> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import _ from 'lodash' |
|||
import dayjs from 'dayjs' |
|||
export default { |
|||
name: 'tablecomponent', |
|||
components: {}, |
|||
emits: ["selectChange", "edit", "info", "del", "pageChange"], |
|||
props: { |
|||
tableHeader: { |
|||
type: Array, |
|||
default: () => { |
|||
return [] |
|||
} |
|||
}, |
|||
pageVO: { |
|||
type: Object, |
|||
default: () => { |
|||
return { |
|||
total: 0, |
|||
currentPage: 1, |
|||
pageSize: 16, |
|||
pageSizes: [16, 30, 50, 100] |
|||
} |
|||
} |
|||
}, |
|||
tableData: { |
|||
type: Array, |
|||
default: () => { |
|||
return [] |
|||
} |
|||
}, |
|||
}, |
|||
data() { |
|||
return { |
|||
_: _, |
|||
dayjs: dayjs, |
|||
} |
|||
}, |
|||
methods: { |
|||
clearSelection() { |
|||
const $table = this.$refs.tableRef |
|||
if ($table) { |
|||
$table.clearCheckboxRow() |
|||
} |
|||
}, |
|||
selectChange({ checked }) { |
|||
let $table = this.$refs.tableRef |
|||
if ($table) { |
|||
let selection = $table.getCheckboxRecords() |
|||
this.$emit("selectChange", selection) |
|||
} |
|||
}, |
|||
selectAllChangeEvent({ checked }) { |
|||
let $table = this.$refs.tableRef |
|||
if ($table) { |
|||
let selection = $table.getCheckboxRecords() |
|||
this.$emit("selectChange", selection) |
|||
} |
|||
}, |
|||
optClick(row, type) { |
|||
this.$emit(type, row) |
|||
}, |
|||
pageChange({ pageSize, currentPage }) { |
|||
this.$emit("pageChange", { pageSize, currentPage }) |
|||
}, |
|||
}, |
|||
async mounted() { }, |
|||
watch: {}, |
|||
computed: { |
|||
footerData() { |
|||
|
|||
return [{ |
|||
"货号": '合计', |
|||
"销售数量": _.sumBy(this.tableData, '销售数量').toFixed(2), |
|||
"销售金额": _.sumBy(this.tableData, '销售金额').toFixed(2), |
|||
"退货数量": _.sumBy(this.tableData, '退货数量').toFixed(2), |
|||
"退货金额": _.sumBy(this.tableData, '退货金额').toFixed(2), |
|||
"数量小计": _.sumBy(this.tableData, '数量小计').toFixed(2), |
|||
"金额小计": _.sumBy(this.tableData, '金额小计').toFixed(2), |
|||
}] |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
<style scoped> |
|||
.tableClass { |
|||
height: calc(100vh - 100px); |
|||
text-align: center; |
|||
text-align-last: center; |
|||
} |
|||
</style> |
@ -0,0 +1,33 @@ |
|||
// main.ts
|
|||
import { createApp } from 'vue' |
|||
import ElementPlus from 'element-plus' |
|||
import zhCn from 'element-plus/es/locale/lang/zh-cn' |
|||
import 'element-plus/dist/index.css' |
|||
import App from './App.vue' |
|||
import './assets/css/base.css' |
|||
import * as ElementPlusIconsVue from '@element-plus/icons-vue' |
|||
import VxeUI from 'vxe-pc-ui' |
|||
import 'vxe-pc-ui/lib/style.css' |
|||
// ...
|
|||
|
|||
// 完整导入 表格库
|
|||
import VxeUITable from 'vxe-table' |
|||
import 'vxe-table/lib/style.css' |
|||
|
|||
const app = createApp(App) |
|||
// window.__VUE_PROD_HYDRATION_MISMATCH_DETAILS__ = true
|
|||
for (const [key, component] of Object.entries(ElementPlusIconsVue)) { |
|||
app.component(key, component) |
|||
} |
|||
app.use(ElementPlus, { |
|||
locale: zhCn, |
|||
}) |
|||
app.use(VxeUI).use(VxeUITable) |
|||
app.mount('#app') |
|||
|
|||
document.addEventListener('keydown', (event) => { |
|||
const { ctrlKey, shiftKey, key } = event |
|||
if ((ctrlKey && shiftKey && key === 'I') || key === 'F12') { |
|||
return event.preventDefault() |
|||
} |
|||
}) |
@ -0,0 +1,430 @@ |
|||
<template> |
|||
<div class="rightClass"> |
|||
<el-row> |
|||
<el-col :span="24"> |
|||
<div class="echartsClass1"> |
|||
<div id="echarts1" class="echartsClass1"></div> |
|||
</div> |
|||
</el-col> |
|||
<el-col :span="12"> |
|||
<div class="echartsClass2"> |
|||
<div id="echarts2" class="echartsClass2"></div> |
|||
</div> |
|||
</el-col> |
|||
<el-col :span="12"> |
|||
<div class="echartsClass2"> |
|||
<div id="echarts3" class="echartsClass2"></div> |
|||
</div> |
|||
</el-col> |
|||
</el-row> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import * as echarts from 'echarts'; |
|||
import { markRaw } from "vue"; |
|||
export default { |
|||
name: 'dashboard', |
|||
components: {}, |
|||
watch: {}, |
|||
computed: {}, |
|||
data() { |
|||
return { |
|||
ecahrts1: null, |
|||
ecahrts2: null, |
|||
ecahrts3: null, |
|||
} |
|||
}, |
|||
methods: { |
|||
init() { |
|||
this.dealEcharts1() |
|||
this.dealEcharts2() |
|||
this.dealEcharts3() |
|||
}, |
|||
dealEcharts1() { |
|||
let option = { |
|||
color: ['#80FFA5', '#00DDFF', '#37A2FF', '#FF0087', '#FFBF00'], |
|||
title: { |
|||
text: '销售统计' |
|||
}, |
|||
legend: { |
|||
data: ['第一季度', '第二季度', '第三季度', '第四季度'] |
|||
}, |
|||
grid: { |
|||
left: '3%', |
|||
right: '4%', |
|||
bottom: '3%', |
|||
containLabel: true |
|||
}, |
|||
xAxis: [ |
|||
{ |
|||
type: 'category', |
|||
boundaryGap: false, |
|||
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] |
|||
} |
|||
], |
|||
yAxis: [ |
|||
{ |
|||
type: 'value' |
|||
} |
|||
], |
|||
series: [ |
|||
{ |
|||
name: '第一季度', |
|||
type: 'line', |
|||
stack: 'Total', |
|||
smooth: true, |
|||
lineStyle: { |
|||
width: 0 |
|||
}, |
|||
showSymbol: false, |
|||
areaStyle: { |
|||
opacity: 0.8, |
|||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
|||
{ |
|||
offset: 0, |
|||
color: 'rgb(128, 255, 165)' |
|||
}, |
|||
{ |
|||
offset: 1, |
|||
color: 'rgb(1, 191, 236)' |
|||
} |
|||
]) |
|||
}, |
|||
emphasis: { |
|||
focus: 'series' |
|||
}, |
|||
data: [140, 232, 101, 264, 90, 340, 250] |
|||
}, |
|||
{ |
|||
name: '第二季度', |
|||
type: 'line', |
|||
stack: 'Total', |
|||
smooth: true, |
|||
lineStyle: { |
|||
width: 0 |
|||
}, |
|||
showSymbol: false, |
|||
areaStyle: { |
|||
opacity: 0.8, |
|||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
|||
{ |
|||
offset: 0, |
|||
color: 'rgb(0, 221, 255)' |
|||
}, |
|||
{ |
|||
offset: 1, |
|||
color: 'rgb(77, 119, 255)' |
|||
} |
|||
]) |
|||
}, |
|||
emphasis: { |
|||
focus: 'series' |
|||
}, |
|||
data: [120, 282, 111, 234, 220, 340, 310] |
|||
}, |
|||
{ |
|||
name: '第三季度', |
|||
type: 'line', |
|||
stack: 'Total', |
|||
smooth: true, |
|||
lineStyle: { |
|||
width: 0 |
|||
}, |
|||
showSymbol: false, |
|||
areaStyle: { |
|||
opacity: 0.8, |
|||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
|||
{ |
|||
offset: 0, |
|||
color: 'rgb(55, 162, 255)' |
|||
}, |
|||
{ |
|||
offset: 1, |
|||
color: 'rgb(116, 21, 219)' |
|||
} |
|||
]) |
|||
}, |
|||
emphasis: { |
|||
focus: 'series' |
|||
}, |
|||
data: [320, 132, 201, 334, 190, 130, 220] |
|||
}, |
|||
{ |
|||
name: '第四季度', |
|||
type: 'line', |
|||
stack: 'Total', |
|||
smooth: true, |
|||
lineStyle: { |
|||
width: 0 |
|||
}, |
|||
showSymbol: false, |
|||
areaStyle: { |
|||
opacity: 0.8, |
|||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
|||
{ |
|||
offset: 0, |
|||
color: 'rgb(255, 0, 135)' |
|||
}, |
|||
{ |
|||
offset: 1, |
|||
color: 'rgb(135, 0, 157)' |
|||
} |
|||
]) |
|||
}, |
|||
emphasis: { |
|||
focus: 'series' |
|||
}, |
|||
data: [220, 402, 231, 134, 190, 230, 120] |
|||
}, |
|||
] |
|||
}; |
|||
if (this.ecahrts1) { |
|||
this.ecahrts1.setOption(option); |
|||
} else { |
|||
let chartDom = document.getElementById('echarts1'); |
|||
this.ecahrts1 = markRaw(echarts.init(chartDom), null, { renderer: 'webgl' }) |
|||
} |
|||
this.ecahrts1.setOption(option); |
|||
}, |
|||
dealEcharts2() { |
|||
let option = { |
|||
title: { |
|||
text: '销售统计' |
|||
}, |
|||
|
|||
grid: { |
|||
left: 30, |
|||
right: 110, |
|||
bottom: 30, |
|||
containLabel: true |
|||
}, |
|||
|
|||
xAxis: { |
|||
type: 'category', |
|||
splitLine: { |
|||
show: true |
|||
}, |
|||
axisLabel: { |
|||
margin: 30, |
|||
fontSize: 16 |
|||
}, |
|||
boundaryGap: false, |
|||
data: [ |
|||
'1月', |
|||
'2月', |
|||
'3月', |
|||
'4月', |
|||
'5月', |
|||
'6月', |
|||
'7月', |
|||
'8月', |
|||
'9月', |
|||
'10月', |
|||
'11月', |
|||
'12月' |
|||
] |
|||
}, |
|||
yAxis: { |
|||
type: 'value', |
|||
axisLabel: { |
|||
margin: 30, |
|||
fontSize: 16, |
|||
formatter: '{value}万元' |
|||
} |
|||
}, |
|||
series: [ |
|||
{ |
|||
name: '天猫小店泰和苑店', |
|||
symbolSize: 20, |
|||
type: 'line', |
|||
smooth: true, |
|||
emphasis: { |
|||
focus: 'series' |
|||
}, |
|||
endLabel: { |
|||
show: true, |
|||
formatter: '{a}', |
|||
distance: 20 |
|||
}, |
|||
lineStyle: { |
|||
width: 4 |
|||
}, |
|||
data: [80, 30, 40, 10, 30, 40, 10, 30, 40, 20, 20, 10] |
|||
}, |
|||
{ |
|||
name: '天猫小店牡丹园店', |
|||
symbolSize: 20, |
|||
type: 'line', |
|||
smooth: true, |
|||
emphasis: { |
|||
focus: 'series' |
|||
}, |
|||
endLabel: { |
|||
show: true, |
|||
formatter: '{a}', |
|||
distance: 20 |
|||
}, |
|||
lineStyle: { |
|||
width: 4 |
|||
}, |
|||
data: [30, 85, 74, 63, 85, 58, 85, 28, 58, 45, 78, 29] |
|||
}, |
|||
{ |
|||
name: '金叶便利东方红店', |
|||
symbolSize: 20, |
|||
type: 'line', |
|||
smooth: true, |
|||
emphasis: { |
|||
focus: 'series' |
|||
}, |
|||
endLabel: { |
|||
show: true, |
|||
formatter: '{a}', |
|||
distance: 20 |
|||
}, |
|||
lineStyle: { |
|||
width: 4 |
|||
}, |
|||
data: [44, 52, 72, 24, 11, 82, 72, 61, 32, 71, 44, 53] |
|||
}, |
|||
{ |
|||
name: '天恩认养一头牛官旗店', |
|||
symbolSize: 20, |
|||
type: 'line', |
|||
smooth: true, |
|||
emphasis: { |
|||
focus: 'series' |
|||
}, |
|||
endLabel: { |
|||
show: true, |
|||
formatter: '{a}', |
|||
distance: 20 |
|||
}, |
|||
lineStyle: { |
|||
width: 4 |
|||
}, |
|||
data: [82, 21, 53, 53, 64, 43, 34, 24, 41, 73, 53, 24] |
|||
} |
|||
] |
|||
}; |
|||
if (this.ecahrts2) { |
|||
this.ecahrts2.setOption(option); |
|||
} else { |
|||
let chartDom = document.getElementById('echarts2'); |
|||
this.ecahrts2 = markRaw(echarts.init(chartDom), null, { renderer: 'webgl' }) |
|||
} |
|||
this.ecahrts2.setOption(option); |
|||
}, |
|||
dealEcharts3() { |
|||
let option = { |
|||
"legend": { |
|||
"top": "bottom" |
|||
}, |
|||
"toolbox": { |
|||
"show": true, |
|||
"feature": { |
|||
"mark": { |
|||
"show": true |
|||
}, |
|||
"dataView": { |
|||
"show": true, |
|||
"readOnly": false |
|||
}, |
|||
"restore": { |
|||
"show": true |
|||
}, |
|||
"saveAsImage": { |
|||
"show": true |
|||
} |
|||
} |
|||
}, |
|||
"series": [ |
|||
{ |
|||
"name": "店铺统计", |
|||
"type": "pie", |
|||
"radius": [ |
|||
25, |
|||
100 |
|||
], |
|||
"roseType": "area", |
|||
"itemStyle": { |
|||
"borderRadius": 8 |
|||
}, |
|||
"data": [ |
|||
{ |
|||
"value": 40, |
|||
"name": "天猫小店泰和苑店" |
|||
}, |
|||
{ |
|||
"value": 38, |
|||
"name": "天猫小店牡丹园店" |
|||
}, |
|||
{ |
|||
"value": 32, |
|||
"name": "金叶便利东方红店" |
|||
}, |
|||
{ |
|||
"value": 30, |
|||
"name": "天恩认养一头牛官旗店" |
|||
} |
|||
] |
|||
} |
|||
] |
|||
}; |
|||
if (this.ecahrts3) { |
|||
this.ecahrts3.setOption(option); |
|||
} else { |
|||
let chartDom = document.getElementById('echarts3'); |
|||
this.ecahrts3 = markRaw(echarts.init(chartDom), null, { renderer: 'webgl' }) |
|||
} |
|||
this.ecahrts3.setOption(option); |
|||
}, |
|||
resize() { |
|||
if (this.ecahrts1) { |
|||
this.ecahrts1.resize({ |
|||
animation: { |
|||
duration: 2000, |
|||
easing: 'cubicInOut' |
|||
} |
|||
}, true) |
|||
} |
|||
if (this.ecahrts2) { |
|||
this.ecahrts2.resize({ |
|||
animation: { |
|||
duration: 2000, |
|||
easing: 'cubicInOut' |
|||
} |
|||
}, true) |
|||
} |
|||
if (this.ecahrts3) { |
|||
this.ecahrts3.resize({ |
|||
animation: { |
|||
duration: 2000, |
|||
easing: 'cubicInOut' |
|||
} |
|||
}, true) |
|||
} |
|||
} |
|||
}, |
|||
async mounted() { |
|||
this.init() |
|||
window.addEventListener('resize', this.resize) |
|||
}, |
|||
} |
|||
</script> |
|||
<style scoped> |
|||
.rightClass { |
|||
padding: 8px; |
|||
} |
|||
|
|||
.echartsClass1 { |
|||
width: calc(100vw - 200px); |
|||
height: 500px; |
|||
} |
|||
|
|||
.echartsClass2 { |
|||
width: calc(50vw - 100px); |
|||
height: 500px; |
|||
} |
|||
</style> |
@ -0,0 +1,584 @@ |
|||
<template> |
|||
<div class="rightClass"> |
|||
<div> |
|||
<el-row style="margin-bottom: 8px;"> |
|||
<el-col> |
|||
<el-button type="primary" @click="addData" size="large"> |
|||
<el-icon> |
|||
<Plus /> |
|||
</el-icon> |
|||
<span>新增</span> |
|||
</el-button> |
|||
<el-button type="danger" @click="deleteData(selectionData)" :disabled="selectionData.length == 0" |
|||
size="large"> |
|||
<el-icon> |
|||
<Delete /> |
|||
</el-icon> |
|||
<span>删除</span> |
|||
</el-button> |
|||
<el-button type="danger" @click="delAll" size="large"> |
|||
<el-icon> |
|||
<Delete /> |
|||
</el-icon> |
|||
<span>清空</span> |
|||
</el-button> |
|||
<el-button type="primary" @click="openImport" size="large"> |
|||
<el-icon> |
|||
<Delete /> |
|||
</el-icon> |
|||
<span>导入数据</span> |
|||
</el-button> |
|||
<el-button type="primary" @click="exportData" size="large"> |
|||
<el-icon> |
|||
<Delete /> |
|||
</el-icon> |
|||
<span>导出</span> |
|||
</el-button> |
|||
<el-select v-model="searchObj.shopName" placeholder="请选择店铺名" class="selectClass" size="large"> |
|||
<el-option v-for="item in shopOptions" :key="item.value" :label="item.label" :value="item.value"> |
|||
</el-option> |
|||
</el-select> |
|||
<el-select v-model="searchObj.month" placeholder="请选择月份" class="selectClass" size="large"> |
|||
<el-option v-for="item in monthOptions" :key="item.value" :label="item.label" :value="item.value"> |
|||
</el-option> |
|||
</el-select> |
|||
<el-button type="primary" @click="updateSeach" size="large" style="margin-left: 16px;"> |
|||
<el-icon> |
|||
<Delete /> |
|||
</el-icon> |
|||
<span>搜索</span> |
|||
</el-button> |
|||
</el-col> |
|||
</el-row> |
|||
</div> |
|||
<div v-loading="loading"> |
|||
<tablecomponent :tableHeader="tableHeader" :tableData="tableData" @selectChange="selectChange" @edit="edit" |
|||
@info="info" @del="del" @pageChange="pageChange" ref="tableComponentRef" :pageVO="pageVO"> |
|||
</tablecomponent> |
|||
</div> |
|||
<el-dialog v-model="dialogFrom.visible" :title="dialogFrom.title" width="80%" :close-on-click-modal="false"> |
|||
<formcomponent :formHeader="dialogFrom.formHeader" :formData="dialogFrom.formData" |
|||
:disabled="dialogFrom.disabled"> |
|||
</formcomponent> |
|||
<template #footer> |
|||
<div> |
|||
<el-button @click="cancelDialog">取消</el-button> |
|||
<el-button type="primary" @click="submitDialog"> 确认 </el-button> |
|||
</div> |
|||
</template> |
|||
</el-dialog> |
|||
<el-dialog v-model="importShow" title="导入数据" width="80%" :close-on-click-modal="false"> |
|||
<div> |
|||
<el-select v-model="importObj.shopName" placeholder="请选择店铺名" class="selectClass" size="large"> |
|||
<el-option v-for="item in shopOptions" :key="item.value" :label="item.label" :value="item.value"> |
|||
</el-option> |
|||
</el-select> |
|||
<el-select v-model="importObj.month" placeholder="请选择月份" class="selectClass" size="large"> |
|||
<el-option v-for="item in monthOptionsList" :key="item.value" :label="item.label" :value="item.value"> |
|||
</el-option> |
|||
</el-select> |
|||
<el-upload :show-file-list="false" v-model="fileOriData" :before-upload="beforeAvatarUpload" |
|||
:http-request="successSubmit" accept=".xls,.xlsx,.csv" |
|||
style="display: inline-block;position: relative;top: 3px;left: 12px;margin-right: 24px;" multiple> |
|||
<el-button type="success" size="large"> |
|||
<el-icon> |
|||
<Upload /> |
|||
</el-icon> |
|||
<span>上传表格</span> |
|||
</el-button> |
|||
</el-upload> |
|||
</div> |
|||
<template #footer> |
|||
<div> |
|||
<el-button @click="cancelDialog">取消</el-button> |
|||
<el-button type="primary" @click="submitImport" :disabled="importCount === 0"> |
|||
确认({{ importCount }}) |
|||
</el-button> |
|||
</div> |
|||
</template> |
|||
</el-dialog> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import _ from 'lodash' |
|||
import * as XLSX from 'xlsx'; |
|||
import tableHeaderLocal from '../assets/json/sale.json' |
|||
import { myDatabase } from '../assets/js/db.js' |
|||
import dayjs from 'dayjs' |
|||
import tablecomponent from "../components/tablecomponent.vue" |
|||
import formcomponent from "../components/formcomponent.vue" |
|||
import { ElMessage, ElMessageBox } from 'element-plus' |
|||
export default { |
|||
name: 'sale', |
|||
components: { tablecomponent, formcomponent }, |
|||
watch: {}, |
|||
computed: {}, |
|||
data() { |
|||
return { |
|||
_: _, |
|||
dayjs: dayjs, |
|||
tableHeader: [], |
|||
formHeader: [], |
|||
tableData: [], |
|||
selectionData: [], |
|||
importShow: false, |
|||
dialogFrom: { |
|||
visible: false, |
|||
title: "新增数据堆", |
|||
type: "add", |
|||
formHeader: [], |
|||
formData: {} |
|||
}, |
|||
fileData: [], |
|||
pageVO: { |
|||
total: 0, |
|||
currentPage: 1, |
|||
pageSize: 16, |
|||
pageSizes: [16, 30, 50, 100] |
|||
}, |
|||
searchObj: { |
|||
shopName: "", |
|||
month: "" |
|||
}, |
|||
importObj: { |
|||
shopName: "", |
|||
month: "" |
|||
}, |
|||
shopOptions: [ |
|||
{ |
|||
label: "天猫小店泰和苑店", |
|||
value: "1" |
|||
}, |
|||
{ |
|||
label: "天猫小店牡丹园店", |
|||
value: "2" |
|||
}, |
|||
{ |
|||
label: "金叶便利东方红店", |
|||
value: "3" |
|||
}, |
|||
{ |
|||
label: "天恩认养一头牛官旗店", |
|||
value: "4" |
|||
} |
|||
], |
|||
monthOptions: [ |
|||
{ |
|||
label: "全部", |
|||
value: "" |
|||
}, |
|||
{ |
|||
label: "1月", |
|||
value: "1月" |
|||
}, |
|||
{ |
|||
label: "2月", |
|||
value: "2月" |
|||
}, |
|||
{ |
|||
label: "3月", |
|||
value: "3月" |
|||
}, |
|||
{ |
|||
label: "4月", |
|||
value: "4月" |
|||
}, |
|||
{ |
|||
label: "5月", |
|||
value: "5月" |
|||
}, |
|||
{ |
|||
label: "6月", |
|||
value: "6月" |
|||
}, |
|||
{ |
|||
label: "7月", |
|||
value: "7月" |
|||
}, |
|||
{ |
|||
label: "8月", |
|||
value: "8月" |
|||
}, |
|||
{ |
|||
label: "9月", |
|||
value: "9月" |
|||
}, |
|||
{ |
|||
label: "10月", |
|||
value: "10月" |
|||
}, |
|||
{ |
|||
label: "11月", |
|||
value: "11月" |
|||
}, |
|||
{ |
|||
label: "12月", |
|||
value: "12月" |
|||
}, |
|||
], |
|||
monthOptionsList: [ |
|||
{ |
|||
label: "1月", |
|||
value: "1月" |
|||
}, |
|||
{ |
|||
label: "2月", |
|||
value: "2月" |
|||
}, |
|||
{ |
|||
label: "3月", |
|||
value: "3月" |
|||
}, |
|||
{ |
|||
label: "4月", |
|||
value: "4月" |
|||
}, |
|||
{ |
|||
label: "5月", |
|||
value: "5月" |
|||
}, |
|||
{ |
|||
label: "6月", |
|||
value: "6月" |
|||
}, |
|||
{ |
|||
label: "7月", |
|||
value: "7月" |
|||
}, |
|||
{ |
|||
label: "8月", |
|||
value: "8月" |
|||
}, |
|||
{ |
|||
label: "9月", |
|||
value: "9月" |
|||
}, |
|||
{ |
|||
label: "10月", |
|||
value: "10月" |
|||
}, |
|||
{ |
|||
label: "11月", |
|||
value: "11月" |
|||
}, |
|||
{ |
|||
label: "12月", |
|||
value: "12月" |
|||
}, |
|||
], |
|||
tableName: "", |
|||
loading: false, |
|||
fileOriData: null, |
|||
importCount: 0, |
|||
} |
|||
}, |
|||
methods: { |
|||
openImport() { |
|||
this.importCount = 0 |
|||
this.fileData = [] |
|||
this.importShow = true |
|||
}, |
|||
beforeAvatarUpload(rawFile) { |
|||
let imgList = ['text/csv', 'application/vnd.ms-excel', "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"] |
|||
if (imgList.indexOf(rawFile.type) === -1) { |
|||
this.$msgbox.alert('请上传excel,csv格式的表格文件!') |
|||
return false |
|||
} else if (rawFile.size / 1024 / 1024 > 50) { |
|||
this.$msgbox.alert('表格文件的大小为小于50MB,数据过多时会处理过慢') |
|||
return true |
|||
} |
|||
return true |
|||
}, |
|||
//表格增加数据 |
|||
async successSubmit(opts) { |
|||
let that = this |
|||
let file = opts.file |
|||
this.fileDealData = [] |
|||
let fileReader = new FileReader() |
|||
fileReader.onload = async function (e) { |
|||
let data = this.result |
|||
let workbook = XLSX.read(data, { type: 'binary' }) |
|||
let sheetName = workbook.SheetNames[0] |
|||
let sheetData = XLSX.utils.sheet_to_json(workbook.Sheets[sheetName]) |
|||
that.fileData = _.concat(that.fileData, sheetData) |
|||
that.importCount++ |
|||
} |
|||
fileReader.onerror = function (error) { |
|||
ElMessage({ |
|||
message: `Error reading file:, ${error}`, |
|||
type: 'error', |
|||
}) |
|||
} |
|||
fileReader.readAsArrayBuffer(file) |
|||
}, |
|||
//更新数据 |
|||
async updateSeach() { |
|||
this.loading = true |
|||
let collection = myDatabase.sale |
|||
let shopNameTemp = _.trim(_.trim(this.searchObj.shopName)) |
|||
let monthTemp = _.trim(_.trim(this.searchObj.month)) |
|||
if (shopNameTemp) { |
|||
collection = collection.filter((equip) => equip.shopName === shopNameTemp) |
|||
} |
|||
if (monthTemp) { |
|||
collection = collection.filter((equip) => equip.month === monthTemp) |
|||
} |
|||
this.pageVO.total = await collection.count() |
|||
this.tableData = await collection.offset((this.pageVO.currentPage - 1) * this.pageVO.pageSize).limit(this.pageVO.pageSize).toArray() |
|||
this.pageVO.pageSizes[5] = this.pageVO.total |
|||
this.loading = false |
|||
}, |
|||
//选中数据 |
|||
selectChange(selection) { |
|||
this.selectionData = _.cloneDeep(selection) |
|||
}, |
|||
// 导出数据 |
|||
async exportData() { |
|||
let listCopy = _.cloneDeep(this.tableData) |
|||
if (listCopy.length) { |
|||
let allDataList = [] |
|||
for (let i = 0; i < listCopy.length; i++) { |
|||
let listItem = listCopy[i]; |
|||
let item = {} |
|||
for (let j = 0; j < this.tableHeader.length; j++) { |
|||
let headerItem = this.tableHeader[j]; |
|||
item[headerItem.label] = listItem[headerItem.prop] |
|||
} |
|||
allDataList.push({ ...item }) |
|||
} |
|||
let jsonWorkSheet = XLSX.utils.json_to_sheet(allDataList); |
|||
let workBook = { |
|||
SheetNames: ["sheet1"], |
|||
Sheets: { |
|||
["sheet1"]: jsonWorkSheet, |
|||
} |
|||
}; |
|||
XLSX.writeFile(workBook, `销售表格${dayjs().format("YYYY-MM-DD_HH-mm-ss")}.xlsx`); |
|||
} else { |
|||
ElMessage({ |
|||
type: 'error', |
|||
message: '当前表格无数据,请有数据后在导出', |
|||
}) |
|||
} |
|||
|
|||
}, |
|||
//切换数据 |
|||
async pageChange({ pageSize, currentPage }) { |
|||
this.pageVO.currentPage = currentPage |
|||
this.pageVO.pageSize = pageSize |
|||
await this.updateSeach() |
|||
}, |
|||
//关闭模态框 |
|||
cancelDialog() { |
|||
this.importCount = 0 |
|||
this.fileData = [] |
|||
this.importShow = false |
|||
this.dialogFrom = { |
|||
visible: false, |
|||
title: "新增", |
|||
type: "add", |
|||
formHeader: [], |
|||
formData: {} |
|||
} |
|||
}, |
|||
//确认提交 |
|||
async submitImport() { |
|||
let that = this |
|||
let dealDataObj = { |
|||
"销售数量": 1.2, |
|||
"销售金额": 1.2, |
|||
"退货数量": 1, |
|||
"退货金额": 1, |
|||
"数量小计": 1.2, |
|||
"金额小计": 1.2, |
|||
} |
|||
let allList = [] |
|||
for (let i = 0; i < this.fileData.length; i++) { |
|||
let element = this.fileData[i]; |
|||
let item = {} |
|||
for (let key in element) { |
|||
item[_.trim(key)] = _.trim(element[key]) |
|||
} |
|||
if (item["行号"] && item["品名"] && item["供应商名称"]) { |
|||
for (let key in item) { |
|||
if (dealDataObj[_.trim(key)]) { |
|||
item[_.trim(key)] = Number((Number(_.trim(element[key])) * dealDataObj[_.trim(key)]).toFixed(2)) |
|||
} |
|||
} |
|||
allList.push({ |
|||
shopName: that.importObj.shopName, |
|||
month: that.importObj.month, |
|||
...item, |
|||
create_at: dayjs().format('YYYY-MM-DD HH:mm:ss'), |
|||
update_at: dayjs().format('YYYY-MM-DD HH:mm:ss') |
|||
}) |
|||
} |
|||
} |
|||
try { |
|||
await myDatabase.sale.bulkAdd(allList) |
|||
this.cancelDialog() |
|||
await that.updateSeach() |
|||
|
|||
} catch (error) { |
|||
ElMessage({ |
|||
message: `销售数据重复,error:${error}`, |
|||
type: 'error', |
|||
}) |
|||
} |
|||
await that.updateSeach() |
|||
|
|||
}, |
|||
//确认模态框 |
|||
async submitDialog() { |
|||
let params = { ...this.dialogFrom.formData, } |
|||
try { |
|||
await myDatabase.sale[this.dialogFrom.type]({ ...params, update_at: dayjs().format('YYYY-MM-DD HH:mm:ss') }) |
|||
await this.updateSeach() |
|||
this.cancelDialog() |
|||
} catch (e) { |
|||
ElMessage( |
|||
{ |
|||
type: "error", |
|||
message: "时间不允许重复,请重新设置时间" |
|||
} |
|||
) |
|||
} |
|||
}, |
|||
//打开新增模态框 |
|||
addData() { |
|||
let formData = {} |
|||
for (let i = 0; i < this.formHeader.length; i++) { |
|||
let element = this.formHeader[i]; |
|||
if (element.type === "text") { |
|||
formData[element.prop] = "" |
|||
} else if (element.type === "date") { |
|||
formData[element.prop] = dayjs().valueOf() |
|||
} |
|||
} |
|||
formData["create_at"] = dayjs().valueOf() |
|||
this.dialogFrom = { |
|||
visible: true, |
|||
title: "新增", |
|||
type: "add", |
|||
disabled: false, |
|||
formHeader: this.formHeader, |
|||
formData |
|||
} |
|||
}, |
|||
//打开编辑数据模态框 |
|||
edit(row) { |
|||
this.dialogFrom = { |
|||
visible: true, |
|||
title: `编辑${row['货号']}`, |
|||
type: "put", |
|||
disabled: false, |
|||
formHeader: this.formHeader, |
|||
formData: { ...row } |
|||
} |
|||
}, |
|||
//打开查看数据模态框 |
|||
info(row) { |
|||
this.dialogFrom = { |
|||
visible: true, |
|||
title: `查看${row['货号']}`, |
|||
type: "info", |
|||
disabled: true, |
|||
formHeader: this.formHeader, |
|||
formData: { ...row } |
|||
} |
|||
}, |
|||
//删除数据 |
|||
del(row) { |
|||
this.deleteData([row]) |
|||
}, |
|||
//批量删除数据 |
|||
deleteData(delList) { |
|||
let tooltipList = [] |
|||
let idList = [] |
|||
for (let i = 0; i < delList.length; i++) { |
|||
let element = delList[i]; |
|||
tooltipList.push(element["货号"]) |
|||
idList.push(element.id) |
|||
} |
|||
ElMessageBox.confirm( |
|||
`是否删除(${_.join(tooltipList, ",")})?`, |
|||
'danger', |
|||
{ |
|||
confirmButtonText: '确认', |
|||
cancelButtonText: '取消', |
|||
type: 'danger', |
|||
} |
|||
) |
|||
.then(async () => { |
|||
this.$refs.tableComponentRef.clearSelection() |
|||
await myDatabase.sale.bulkDelete(idList) |
|||
await this.updateSeach() |
|||
}) |
|||
.catch((err) => { |
|||
ElMessage({ |
|||
type: 'info', |
|||
message: '取消删除', |
|||
}) |
|||
}) |
|||
|
|||
}, |
|||
//删除全部数据 |
|||
delAll() { |
|||
ElMessageBox.confirm( |
|||
`是否删除全部数据?此操作不可逆!`, |
|||
'危险操作', |
|||
{ |
|||
confirmButtonText: '确认', |
|||
cancelButtonText: '取消', |
|||
type: 'danger', |
|||
} |
|||
) |
|||
.then(async () => { |
|||
this.$refs.tableComponentRef.clearSelection() |
|||
await myDatabase.sale.clear() |
|||
await this.updateSeach() |
|||
}) |
|||
.catch(() => { |
|||
ElMessage({ |
|||
type: 'info', |
|||
message: '取消删除', |
|||
}) |
|||
}) |
|||
}, |
|||
}, |
|||
async mounted() { |
|||
this.tableHeader = _.filter(tableHeaderLocal, o => o.tableShow) |
|||
this.formHeader = _.filter(tableHeaderLocal, o => o.formShow) |
|||
await this.updateSeach() |
|||
}, |
|||
} |
|||
</script> |
|||
<style scoped> |
|||
.rightClass { |
|||
padding: 8px; |
|||
} |
|||
|
|||
.uploadClass { |
|||
top: 3px; |
|||
} |
|||
|
|||
.inputClass { |
|||
display: inline-block; |
|||
margin: 0 8px; |
|||
position: relative; |
|||
} |
|||
|
|||
.tagClass { |
|||
display: inline-block; |
|||
margin: 0 8px; |
|||
width: 200px; |
|||
position: relative; |
|||
} |
|||
|
|||
.selectClass { |
|||
display: inline-block; |
|||
max-width: 200px; |
|||
margin-left: 16px; |
|||
} |
|||
</style> |
Loading…
Reference in new issue