commit c021e6ce483a8445a1ef4c0f3337dbf446ba2352 Author: hujiabin Date: Mon Jul 18 19:21:28 2022 +0800 init diff --git a/.env b/.env new file mode 100644 index 0000000..6a8f6c0 --- /dev/null +++ b/.env @@ -0,0 +1,3 @@ +VUE_APP_API_HOST=10.0.235.4:9000 +VUE_APP_TITLE=包管理系统 +VUE_APP_TITLE_EN=PROJECT PACKAGE MANAGER \ No newline at end of file diff --git a/.env.production b/.env.production new file mode 100644 index 0000000..e917110 --- /dev/null +++ b/.env.production @@ -0,0 +1,3 @@ +VUE_APP_API_HOST=127.0.0.1:8000 +VUE_APP_TITLE=包管理系统 +VUE_APP_TITLE_EN=PROJECT PACKAGE MANAGER \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..33e0b0b --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +/node_modules/ +.idea +dist/ +/package-lock.json +/.env.development \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..39f0490 --- /dev/null +++ b/README.md @@ -0,0 +1,19 @@ +# project package manager + +## Project setup +``` +npm install +``` + +### Compiles and hot-reloads for development +``` +npm run serve +``` + +### Compiles and minifies for production +``` +npm run build +``` + +### Customize configuration +See [Configuration Reference](https://cli.vuejs.org/config/). diff --git a/babel.config.js b/babel.config.js new file mode 100644 index 0000000..97b4db5 --- /dev/null +++ b/babel.config.js @@ -0,0 +1,5 @@ +module.exports = { + presets: [ + '@vue/app' + ] +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..25ce730 --- /dev/null +++ b/package.json @@ -0,0 +1,63 @@ +{ + "name": "hola_admin", + "version": "0.1.0", + "private": true, + "scripts": { + "serve": "vue-cli-service serve", + "build": "vue-cli-service build", + "build:dev": "vue-cli-service build --mode=test" + }, + "dependencies": { + "autoprefixer": "^9.8.6", + "axios": "^0.21.1", + "ckeditor4-vue": "^1.2.0", + "core-js": "^3.6.5", + "crypto-js": "^4.0.0", + "echarts": "^5.2.1", + "element-ui": "^2.14.1", + "js-sha512": "^0.8.0", + "lodash": "^4.17.21", + "postcss": "^7.0.36", + "sprintf-js": "^1.1.2", + "tailwindcss": "npm:@tailwindcss/postcss7-compat@2.1.0", + "vant": "^2.12.10", + "view-design": "^4.5.0", + "vue": "^2.6.11", + "vue-class-component": "^7.2.3", + "vue-i18n": "^8.22.2", + "vue-property-decorator": "^8.4.2", + "vue-router": "^3.2.0", + "vuedraggable": "2.20.0", + "vuex": "^3.4.0", + "vuex-class": "^0.3.2", + "vuex-persistedstate": "^4.0.0-beta.1", + "web3": "^1.7.4", + "weixin-js-sdk": "^1.6.0" + }, + "devDependencies": { + "@tailwindcss/postcss7-compat": "^2.2.6", + "@types/echarts": "^4.9.10", + "@types/lodash": "^4.14.171", + "@vue/cli-plugin-babel": "~4.5.0", + "@vue/cli-plugin-router": "~4.5.0", + "@vue/cli-plugin-typescript": "~4.5.0", + "@vue/cli-plugin-vuex": "~4.5.0", + "@vue/cli-service": "~4.5.0", + "babel-plugin-component": "^1.1.1", + "less": "^3.0.4", + "less-loader": "^5.0.0", + "svg-sprite-loader": "4.1.3", + "svgo": "^2.3.1", + "typescript": "~3.9.3", + "vue-cli-plugin-iview": "^2.0.0", + "vue-template-compiler": "^2.6.11", + "vuex-module-decorators": "^1.0.1" + }, + "browserslist": [ + "last 2 version", + "> 1%", + "iOS >= 7", + "Android > 4.1", + "Firefox > 20" + ] +} diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 0000000..5a3d19f --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} \ No newline at end of file diff --git a/public/.htaccess b/public/.htaccess new file mode 100644 index 0000000..de4c3aa --- /dev/null +++ b/public/.htaccess @@ -0,0 +1,10 @@ + + Options +FollowSymlinks -Multiviews + RewriteEngine On + + RewriteCond %{REQUEST_FILENAME} !-d + RewriteCond %{REQUEST_FILENAME} !-f + + RewriteRule ^(.*)$ index.html [QSA,PT,L] + + \ No newline at end of file diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..df36fcf Binary files /dev/null and b/public/favicon.ico differ diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..d13d0e7 --- /dev/null +++ b/public/index.html @@ -0,0 +1,16 @@ + + + + + + + <%= VUE_APP_TITLE %> + + + +
+ + + diff --git a/public/wap.html b/public/wap.html new file mode 100644 index 0000000..8eb2ef9 --- /dev/null +++ b/public/wap.html @@ -0,0 +1,16 @@ + + + + + + + <%= VUE_APP_TITLE %> + + + +
+ + + diff --git a/src/app.d.ts b/src/app.d.ts new file mode 100644 index 0000000..405094e --- /dev/null +++ b/src/app.d.ts @@ -0,0 +1,21 @@ +import VueRouter from 'vue-router' +import { Toast } from "vant"; +import * as _ from 'lodash' + +declare module "vue/types/vue" { + import { Route } from "vue-router"; + import {RequestInterface} from "@/views/admin/types/Types"; + import VueI18n from "vue-i18n"; + + interface Vue { + $router : VueRouter; + $route : Route; + $http : RequestInterface, + $lang: Function, + $sprintf: Function, + $toast : Toast, + $_ : _, + $bus: any, + $isDev: boolean + } +} diff --git a/src/assets/image/dashboard/script.svg b/src/assets/image/dashboard/script.svg new file mode 100644 index 0000000..8e86cbc --- /dev/null +++ b/src/assets/image/dashboard/script.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/image/dashboard/任务.svg b/src/assets/image/dashboard/任务.svg new file mode 100644 index 0000000..145da82 --- /dev/null +++ b/src/assets/image/dashboard/任务.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/image/dashboard/心-男手机.svg b/src/assets/image/dashboard/心-男手机.svg new file mode 100644 index 0000000..6d3b86e --- /dev/null +++ b/src/assets/image/dashboard/心-男手机.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/image/dashboard/项目信息.svg b/src/assets/image/dashboard/项目信息.svg new file mode 100644 index 0000000..f886d26 --- /dev/null +++ b/src/assets/image/dashboard/项目信息.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/image/del.png b/src/assets/image/del.png new file mode 100644 index 0000000..6979dfd Binary files /dev/null and b/src/assets/image/del.png differ diff --git a/src/assets/image/edit.png b/src/assets/image/edit.png new file mode 100644 index 0000000..24e0e51 Binary files /dev/null and b/src/assets/image/edit.png differ diff --git a/src/assets/image/loginBg.jpg b/src/assets/image/loginBg.jpg new file mode 100644 index 0000000..b5c7027 Binary files /dev/null and b/src/assets/image/loginBg.jpg differ diff --git a/src/assets/js/wwlogin.js b/src/assets/js/wwlogin.js new file mode 100644 index 0000000..b874a15 --- /dev/null +++ b/src/assets/js/wwlogin.js @@ -0,0 +1,17 @@ +! function (a, b, c) { + function d(c) { + var d = b.createElement("iframe"), + e = c.goto; + e += c.style ? "&style=" + c.style : "", e += c.href ? "&href=" + c.href : "", d.src = e, d.frameBorder = "0", d.allowTransparency = "true", d.scrolling = "no", d.width = "300px", d.height = "320px", d.style.transform = 'scale(0.8) translateY(-10%)'; + var f = b.getElementById(c.id); + f.innerHTML = "", f.appendChild(d), d.onload = function () { + a.addEventListener("message", function (b) { + if(b.data && typeof b.data == "string" && b.data.substring(0,4) != 'http'){ + return false; + } + b.data && b.origin.indexOf("work.weixin.qq.com") > -1 && (a.location.href = b.data) + }), d.contentWindow.postMessage("ask_usePostMessage", "*") + } + } + a.WwLogin = d +}(window, document); diff --git a/src/assets/style/main.less b/src/assets/style/main.less new file mode 100644 index 0000000..3b7f2c6 --- /dev/null +++ b/src/assets/style/main.less @@ -0,0 +1,13 @@ +html, +body, +#app { + width: 100%; + height: 100%; + font-family: arial; + background-color: white; +} + +* { + margin: 0; + padding: 0; +} diff --git a/src/assets/style/tailwind.css b/src/assets/style/tailwind.css new file mode 100644 index 0000000..f39f510 --- /dev/null +++ b/src/assets/style/tailwind.css @@ -0,0 +1,33 @@ +@import "tailwindcss/base"; +@import "tailwindcss/components"; +@import "tailwindcss/utilities"; + +.max-w-8xl { + max-width: 90rem; +} +.max-w-4xl { + max-width: 45rem; +} +.max-w-2xl { + max-width: 22.5rem; +} + + +.lg\:h-\(screen-14\) { + height: calc(100vh - 3.5rem); +} + + +@supports ((position: -webkit-sticky) or (position:sticky)) { + @media (min-width:1024px) { + .sticky\?lg\:h-screen { + height:100vh!important + } + lg\:.h-\(screen-14\) { + height: calc(100vh - 3.5rem); + } + .sticky\?lg\:h-\(screen-18\) { + height: calc(100vh - 4.5rem); + } + } +} \ No newline at end of file diff --git a/src/components/DndList/index.vue b/src/components/DndList/index.vue new file mode 100644 index 0000000..72a9b7e --- /dev/null +++ b/src/components/DndList/index.vue @@ -0,0 +1,166 @@ + + + + + diff --git a/src/components/Kanban/index.vue b/src/components/Kanban/index.vue new file mode 100644 index 0000000..92a5fea --- /dev/null +++ b/src/components/Kanban/index.vue @@ -0,0 +1,109 @@ + + + + + diff --git a/src/components/SvgIcon.vue b/src/components/SvgIcon.vue new file mode 100644 index 0000000..fd33fe1 --- /dev/null +++ b/src/components/SvgIcon.vue @@ -0,0 +1,42 @@ + + + + + \ No newline at end of file diff --git a/src/components/page.less b/src/components/page.less new file mode 100644 index 0000000..e69de29 diff --git a/src/components/page.ts b/src/components/page.ts new file mode 100644 index 0000000..a52e7fc --- /dev/null +++ b/src/components/page.ts @@ -0,0 +1,72 @@ +import { Component, Vue, Watch, Prop, Emit, PropSync } from 'vue-property-decorator' +import { + State, + Getter, + Action, + Mutation, +} from "vuex-class"; +import { Table, TableColumn } from "view-design"; +import Events from "@/utils/Events"; +import { TableExportCsvParams } from "view-design/types/table"; +import { stringify } from "postcss"; +import Project from "@/views/home/pages/project/project"; + + +@Component +export default class page extends Vue { + name : string = "CustomPage" + + @Prop({type : Number, default : 0}) + total : number; + + @Prop({default : true}) + showPaginate : boolean + + @PropSync('page', {type : Number}) currentPage! : Number + + @Prop({type : Number, default : 10}) + pageSize : number; + + @Prop({type : Array, default : () => []}) + columns : TableColumn[]; + + @Prop({type : Array, default : () => []}) + dataList : Array; + @Prop({default : false}) + rowKey : Boolean | string + + @Prop({type: String, default: "暂无数据"}) + noDataText: string; + + sizeOpt : number[] = [20, 40, 80, 120]; + + get currentPageSize() { + return this.pageSize; + } + + mounted() { + if (!this.sizeOpt.includes(this.pageSize)) { + this.sizeOpt.push(this.pageSize); + this.sizeOpt = this.sizeOpt.sort((a, b) => a - b); + } + } + + public exportCsv(params : TableExportCsvParams) { + let table : Table = this.$refs["table"] as Table; + table.exportCsv(params); + } + + public onPageSizeChange(size : number) { + this.$emit("update:pageSize", size); + } + + @Emit(Events.ON_PAGE_CHANGE) + public onPageChange(page : number) : number { + return page; + } + + @Emit(Events.ON_SELECTION_CHANGE) + public onSelectionChange(selection : any[]) : any[] { + return selection + } +} \ No newline at end of file diff --git a/src/components/page.vue b/src/components/page.vue new file mode 100644 index 0000000..eecc176 --- /dev/null +++ b/src/components/page.vue @@ -0,0 +1,19 @@ + + + + \ No newline at end of file diff --git a/src/icons/index.ts b/src/icons/index.ts new file mode 100644 index 0000000..be00925 --- /dev/null +++ b/src/icons/index.ts @@ -0,0 +1,10 @@ +import Vue from 'vue' +import SvgIcon from '@/components/SvgIcon.vue'// svg component + +// register globally +Vue.component('svg-icon', SvgIcon) + +const req = require.context('./svg', false, /\.svg$/) +const requireAll = requireContext => requireContext.keys().map(requireContext) +requireAll(req) + diff --git a/src/icons/svg/device.svg b/src/icons/svg/device.svg new file mode 100644 index 0000000..6d3b86e --- /dev/null +++ b/src/icons/svg/device.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/icons/svg/device_busy.svg b/src/icons/svg/device_busy.svg new file mode 100644 index 0000000..2525303 --- /dev/null +++ b/src/icons/svg/device_busy.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/icons/svg/device_free.svg b/src/icons/svg/device_free.svg new file mode 100644 index 0000000..884b544 --- /dev/null +++ b/src/icons/svg/device_free.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/icons/svg/login.svg b/src/icons/svg/login.svg new file mode 100644 index 0000000..1d1c227 --- /dev/null +++ b/src/icons/svg/login.svg @@ -0,0 +1,2 @@ + \ No newline at end of file diff --git a/src/icons/svg/project.svg b/src/icons/svg/project.svg new file mode 100644 index 0000000..f886d26 --- /dev/null +++ b/src/icons/svg/project.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/icons/svg/script.svg b/src/icons/svg/script.svg new file mode 100644 index 0000000..8e86cbc --- /dev/null +++ b/src/icons/svg/script.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/icons/svg/task.svg b/src/icons/svg/task.svg new file mode 100644 index 0000000..145da82 --- /dev/null +++ b/src/icons/svg/task.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/icons/svgo.yml b/src/icons/svgo.yml new file mode 100644 index 0000000..d11906a --- /dev/null +++ b/src/icons/svgo.yml @@ -0,0 +1,22 @@ +# replace default config + +# multipass: true +# full: true + +plugins: + + # - name + # + # or: + # - name: false + # - name: true + # + # or: + # - name: + # param1: 1 + # param2: 2 + +- removeAttrs: + attrs: + - 'fill' + - 'fill-rule' diff --git a/src/interface/User.ts b/src/interface/User.ts new file mode 100644 index 0000000..411c9b9 --- /dev/null +++ b/src/interface/User.ts @@ -0,0 +1,9 @@ +export interface User { + id : number; + name : string; +} + +enum StatusEnum { + DISABLED, + ENABLED +} \ No newline at end of file diff --git a/src/metamask/ChainManager.ts b/src/metamask/ChainManager.ts new file mode 100644 index 0000000..37c2af8 --- /dev/null +++ b/src/metamask/ChainManager.ts @@ -0,0 +1,162 @@ +import store from "@/views/home/store/index"; +import Web3 from 'web3' +import { Message } from 'element-ui'; +import axios from 'axios'; +import {Http_getNonce,Http_login} from "@/utils/login-request" +// import Cookie from "@u/cookie"; +interface Window { + ethereum: any + web3: any + celo: any +} +declare let window: Window + +export default class ChainManager { + public provider:any + public web3:Web3 + public chainId:number + public account:string + public nonce:string + + private async login( account, chainId, nonce ) { + nonce += '' + // console.log(nonce);return + const tips = 'This signature is only used for verify your account' + const signMsg = { + tips, + nonce + } + const EIP721_DOMAIN_DATA = [ + { name: 'name', type: 'string' }, + { name: 'version', type: 'string' } + ] + + const signObj = { + types: { + EIP712Domain: EIP721_DOMAIN_DATA, + set: [ + { name: 'tips', type: 'string' }, + { name: 'nonce', type: 'string' } + ] + }, + primaryType: 'set', + domain: { + name: 'Auth', + version: '1' + }, + message: signMsg + } + const signature = await this.signData(signObj, account) + const authData = { + account, + nonce, + signature, + tips, + net_id: chainId + } + // console.log('login data: ', authData) + const res = await Http_login(authData) + console.log(res) + // if (!res.errcode && res.token) { + // store.commit('set_token',res.token) + // + // } + } + public async checkNance() { + try { + let nonce = store.getters.nonce + // console.log(store.getters);return + if (!nonce) { + let params = {account:this.account, net_id:this.chainId} + const res = await Http_getNonce(params); + // store.commit('set_nonce',res.nonce) + // this.nonce = res.nonce + } + // console.log(this.account,this.chainId,this.nonce);return + this.login(this.account,this.chainId,this.nonce) + } catch (err) { + console.log(err) + Promise.reject(err) + } + } + public async connect(){ + if (!this.hasMetamask()) { + Message({ + message: '请先安装MetaMask插件', + type: 'error', + duration: 5 * 1000 + }) + return + } + this.provider = await this.connectMetaMask() + if (!this.provider) { + return + } + console.log(this.provider);return + this.web3 = new Web3(this.provider) + this.chainId = await this.web3.eth.getChainId() + const accounts = await this.web3.eth.getAccounts() + if (accounts && accounts.length > 0) { + this.account =accounts[0]; + } + } + private async connectMetaMask() { + let provider = window.ethereum + try { + await provider.request({ method: 'eth_requestAccounts' }) + } catch (error) { + if (error.code === -32002) { + throw new Error('MeatMask not login, Open MeatMask and login first') + } else { + throw new Error('User Rejected') + } + } + return provider + } + private verifyInjectedProvider(check) { + return window.ethereum + ? window.ethereum[check] + : window.web3 && + window.web3.currentProvider && + window.web3.currentProvider[check] + } + private hasMetamask() { + if (typeof window.ethereum !== 'undefined') { + return true; + }else { + return false; + } + } + private async signData(signObj, signer) { + const msgParams = JSON.stringify(signObj) + const from = signer + // console.log('clicked, sending personal sign req', 'from', from, msgParams) + const params = [from, msgParams] + const result = await this.sendCmd({ + method: 'eth_signTypedData_v4', + params, + from + }) + console.log(result) + // return result.result + } + private async sendCmd({ method, params, from }) { + return new Promise((resolve, reject) => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + this.web3.currentProvider.sendAsync({ + method, + params, + from + }, async function(err, result) { + if (err) { + reject && reject(err) + return + } + resolve && resolve(result) + }) + }) + } + + +} diff --git a/src/plugins/CKEditor.js b/src/plugins/CKEditor.js new file mode 100644 index 0000000..e33b8d8 --- /dev/null +++ b/src/plugins/CKEditor.js @@ -0,0 +1,3 @@ +import Vue from 'vue'; +import CKEditor from 'ckeditor4-vue' +Vue.use(CKEditor); \ No newline at end of file diff --git a/src/plugins/element.js b/src/plugins/element.js new file mode 100644 index 0000000..e22ad0e --- /dev/null +++ b/src/plugins/element.js @@ -0,0 +1,5 @@ +import 'element-ui/lib/theme-chalk/index.css'; +import Vue from 'vue'; +import ElementUI from 'element-ui'; + +Vue.use(ElementUI); \ No newline at end of file diff --git a/src/plugins/iview.js b/src/plugins/iview.js new file mode 100644 index 0000000..9322d4a --- /dev/null +++ b/src/plugins/iview.js @@ -0,0 +1,5 @@ +import Vue from 'vue' +import ViewUI from 'view-design' + +Vue.use(ViewUI) +import 'view-design/dist/styles/iview.css' diff --git a/src/plugins/vant.js b/src/plugins/vant.js new file mode 100644 index 0000000..a94955e --- /dev/null +++ b/src/plugins/vant.js @@ -0,0 +1,4 @@ +import Vue from 'vue' +import Vant from "vant"; +Vue.use(Vant) +import 'vant/lib/index.css'; \ No newline at end of file diff --git a/src/router/_import_production.ts b/src/router/_import_production.ts new file mode 100644 index 0000000..8ae4463 --- /dev/null +++ b/src/router/_import_production.ts @@ -0,0 +1 @@ +export function _import( file: string) { return import(/* webpackChunkName: "about" */ '../views/About.vue')} \ No newline at end of file diff --git a/src/shims-tsx.d.ts b/src/shims-tsx.d.ts new file mode 100644 index 0000000..c656c68 --- /dev/null +++ b/src/shims-tsx.d.ts @@ -0,0 +1,13 @@ +import Vue, { VNode } from 'vue' + +declare global { + namespace JSX { + // tslint:disable no-empty-interface + interface Element extends VNode {} + // tslint:disable no-empty-interface + interface ElementClass extends Vue {} + interface IntrinsicElements { + [elem: string]: any + } + } +} diff --git a/src/shims-vue.d.ts b/src/shims-vue.d.ts new file mode 100644 index 0000000..d9f24fa --- /dev/null +++ b/src/shims-vue.d.ts @@ -0,0 +1,4 @@ +declare module '*.vue' { + import Vue from 'vue' + export default Vue +} diff --git a/src/utils/Events.ts b/src/utils/Events.ts new file mode 100644 index 0000000..ac1503b --- /dev/null +++ b/src/utils/Events.ts @@ -0,0 +1,7 @@ + +export default class Events { + static readonly ON_PAGE_CHANGE : string = "on-page-change"; + static readonly ON_PAGE_SIZE_CHANGE : string = "on-page-size-change"; + static readonly ON_SELECTION_CHANGE : string = 'on-selection-change'; + static readonly ON_REFRESH_USER: string = 'on-refresh-user' +} \ No newline at end of file diff --git a/src/utils/Http.ts b/src/utils/Http.ts new file mode 100644 index 0000000..039dcc1 --- /dev/null +++ b/src/utils/Http.ts @@ -0,0 +1,190 @@ +import axios, {AxiosError, AxiosInstance, AxiosRequestConfig, AxiosResponse, Method} from "axios"; +import {Store} from "vuex"; +import {VueRouter} from "vue-router/types/router"; +import {ServerRetEnum} from "@/views/admin/types/Types"; + +const sprintf = require('sprintf-js') + +let apiHost = process.env.NODE_ENV !== 'production' ? process.env.VUE_APP_API_HOST : document.location.host; +let scheme = document.location.protocol; + + +class Http { + private readonly _handler : AxiosInstance; + static _instance : Http; + private _store? : Store; + private _router : VueRouter | any; + private _vue : Vue | any; + private _baseUri : string = ''; + + static instance() : Http { + if (!this._instance) { + this._instance = new Http(); + } + return this._instance; + } + + set store(store : Store) { + this._store = store; + } + + set router(router : VueRouter) { + this._router = router; + } + + set vue(vue : Vue) { + this._vue = vue; + } + + get handler () : AxiosInstance { + return this._handler; + } + + get uri() { + // console.log(this._handler.defaults, 'get_uri'); + return this._handler.defaults.baseURL; + } + + set baseUri(uri : string) { + this._baseUri = uri; + } + + get baseUri () : string { + return this._baseUri; + } + + constructor() { + this._handler = axios.create({ + baseURL : scheme + "//" + apiHost, + timeout : 50000, + }); + this._handler.interceptors.response.use((response : AxiosResponse) => { + // console.log(response, '基本请求结果') + if (response.status == 200) { + return response.data; + } else { + // console.log(response, '未知错误') + } + }, (error : AxiosError) => { + let response = error.response; + if (response?.status === 401) { + this._router.replace("/login"); + } else { + console.error('response error', error.response); + } + return Promise.reject(error); + }) + this._handler.interceptors.request.use((config : AxiosRequestConfig) => { + config.headers["X-Requested-With"] = "XMLHttpRequest"; + if (this._store) { + config.headers["Authorization"] = "Bearer " + this._store.getters.token; + } + return config; + }) + } + + public sprintf(...params) : string { + return sprintf.sprintf(...params); + } + + public get(url : string, params? : object | any, ...args : any[]) : Promise { + return this.request('GET', url, params, ...args); + } + + public post(url : string, data? : object | any, ...args : any[]) : Promise { + return this.request('POST', url, data, ...args); + } + + public put(url : string, params? : object | any, ...args : any[]) : Promise { + return this.request('PUT', url, params, ...args); + } + + public delete(url : string, params? : object | any, ...args : any[]) : Promise { + return this.request('DELETE', url, params, ...args); + } + + public request(method : Method, url : string, data? : object | any, headers? : any) : Promise { + url = this._baseUri + url; + let params : AxiosRequestConfig = { + url, method, headers + } + if (method.toLocaleUpperCase() === 'GET') { + params.params = data; + } else { + params.data = data; + } + return new Promise((resolve, reject) => { + this._handler.request(params).then((res : any) => { + if (res.code === ServerRetEnum.SUCCESS) { + resolve(res); + } else if(res.code === ServerRetEnum.NO_PERMISSION) { + if(this._vue.$Message){ + this._vue.$Message.error(res.message); + } + } else { + console.error(res); + reject(res.message); + } + }).catch((err : any) => { + reject(err); + }) + }) + } + + public download(method : Method, url: string) : Promise { + this._handler.interceptors.response.use((response : AxiosResponse) => { + return Promise.resolve(response) + }); + return new Promise((resolve, reject) => { + url = this._baseUri + url; + let params : AxiosRequestConfig = { + url, method, responseType: "blob", timeout: 600e3 + } + this._handler.request(params).then((res : any) => { + resolve(res); + }).catch((reason) => { + console.error(reason); + reject(reason); + }) + }); + } +} + +const instance : Http = Http.instance(); +export default instance; +export const get = instance.get; +export const post = instance.post; + + +export interface ResponseInterface { + data? : Array | object | object[] | any, + code : ServerRetEnum, + message : string +} + +export interface ResponseErrorInterface { + result : number, + message : string +} + +export interface RequestInterface { + store : Store; + router : VueRouter; + handler : AxiosInstance; + baseUri : string; + + instance(); + + + get(url : string, params? : object | any, ...args : any[]) : Promise; + + post(url : string, params? : object | any, ...args : any[]) : Promise; + + put(url : string, params? : object | any, ...args : any[]) : Promise; + + delete(url : string, params? : object | any, ...args : any[]) : Promise; + + request(method : Method, url : string, data? : object | any, headers? : any) : Promise; + + download(method : Method, url : string) : Promise +} \ No newline at end of file diff --git a/src/utils/Parnet.ts b/src/utils/Parnet.ts new file mode 100644 index 0000000..350ceb9 --- /dev/null +++ b/src/utils/Parnet.ts @@ -0,0 +1,21 @@ +import Vue from "vue"; +import { RawLocation } from "vue-router/types/router"; +import { Route } from "vue-router"; + +export default class Parent extends Vue { + protected _push(location : RawLocation, success_cb? : Function, error_cb? : Function) { + this.$router.push(location).catch(err => { + if (error_cb) { + error_cb.apply(this, err); + } + }).then((value : Route) => { + if (success_cb) { + success_cb.apply(this, value); + } + }) + } + + public goPage(name : string, success_cb? : Function, error_cb? : Function) { + this._push({name}, success_cb, error_cb); + } +} \ No newline at end of file diff --git a/src/utils/Url.ts b/src/utils/Url.ts new file mode 100644 index 0000000..fe63ef1 --- /dev/null +++ b/src/utils/Url.ts @@ -0,0 +1,110 @@ +/** + * 前台地址 + */ +export default class Url { + static readonly WORK_LOGIN : string = 'work/login' + static readonly LOGIN : string = "login"; + static readonly LOGOUT : string = "logout"; + // TODO 菜单 + static readonly MENUS : string = "menu"; + static readonly DELETE_MENU : string = "menu/%d"; + static readonly SHOW_MENU : string = "menu/%d"; + static readonly UPDATE_MENU : string = "menu/%d"; + // TODO 用户 + static readonly USERS : string = "user"; + static readonly DISABLE_USER : string = 'user/%d'; + static readonly ENABLE_USER : string = "user/start/%d"; + static readonly SHOW_USER : string = 'user/%d'; + static readonly UPDATE_USER : string = 'user/%d'; + static readonly UPDATE_USERNAME : string = 'user/update_name/%d'; + + + static readonly MY_MENUS : string = "my_menu"; + static readonly MY_POWER : string = "my_power"; + static readonly MY_PROJECT : string = "my_project"; + static readonly UPDATE_MY_NAME : string = "update/my/name"; + + + // TODO 权限 + static readonly POWER : string = "power"; + static readonly SHOW_POWER : string = "power/%d"; + static readonly UPDATE_POWER : string = "power/%d"; + static readonly DELETE_POWER : string = "power/%d"; + + // TODO 权限组 + static readonly POWER_GROUP : string = "power_group"; + static readonly SHOW_POWER_GROUP : string = "power_group/%d"; + static readonly UPDATE_POWER_GROUP : string = "power_group/%d"; + static readonly DELETE_POWER_GROUP : string = "power_group/%d"; + + // TODO 项目 + static readonly PROJECT : string = 'project'; + static readonly SHOW_PROJECT : string = "project/%d"; + static readonly UPDATE_PROJECT : string = "project/%d"; + static readonly DELETE_PROJECT : string = "project/%d"; + + static readonly WATCH_PROJECT : string = "project/watch/%d"; + static readonly UN_WATCH_PROJECT : string = "project/watch/%d"; + static readonly SHOW_PROJECT_ITEM : string = "project_item/%d"; + static readonly DElETE_PROJECT_ITEM : string = "project_item/%d"; + static readonly DElETE_PROJECT_PRODUCT : string = "project_product/%d"; + static readonly UPDATE_PROJECT_PRODUCT : string = "project_product/%d"; + static readonly SHOW_PROJECT_PRODUCT : string = "project_product/%d"; + static readonly DOWNLOAD_PROJECT_PRODUCT : string = "project_product/download/%d"; + static readonly SHOW_PRODUCT_Log : string = "show_log/%d"; + static readonly SHOW_DOWNLOAD_IP : string = "product/download_ip/%d"; + static readonly DOWNLOAD_IP : string = "download_ip"; + static readonly DELETE_DOWNLOAD_IP : string = "download_ip/%d"; + static readonly UPDATE_DOWNLOAD_IP : string = "download_ip/%d"; + + + //TODO 用户分配 + static readonly ALLOT_MENT : string = 'allot_menu/%d'; + static readonly ALLOT_POWER : string = 'allot_power/%d'; + static readonly ALLOT_PROJECT : string = 'allot_project/%d'; + + + //TODO 自动化(设备) + static readonly DEVICE : string = 'device'; + static readonly UPLOAD_IMAGE : string = 'device/upload_pic'; + static readonly DELETE_DEVICE : string = 'device/%d'; + static readonly UPDATE_DEVICE : string = 'device/%d'; + + //TODO 自动化(脚本) + static readonly SCRIPT : string = 'script'; + static readonly UPLOAD_SCRIPT : string = 'script/upload'; + static readonly UPDATE_SCRIPT : string = 'script/%d'; + static readonly DELETE_SCRIPT : string = 'script/%d'; + static readonly DOWNLOAD_SCRIPT : string = 'script/download/%d'; + + //TODO 自动化(脚本集) + static readonly SCRIPT_COLLECTION : string = 'collection'; + static readonly UPDATE_SCRIPT_COLLECTION : string = 'collection/%d'; + static readonly DELETE_SCRIPT_COLLECTION : string = 'collection/%d'; + static readonly DOWNLOAD_SCRIPT_COLLECTION : string = 'collection/download/%d'; + + //TODO 自动化(任务) + static readonly TASK : string = 'task'; + static readonly UPDATE_TASK : string = 'task/%d'; + static readonly DELETE_TASK : string = 'task/%d'; + static readonly TASK_LOG : string = 'task_log'; + static readonly TASK_DEFAULT_STATUS_CHANGE : string = 'task/set_default/%d' + static readonly TASK_REPORT : string = 'task_report/%d' + + //TODO 自动化(数据仓库) + static readonly DATA :string = 'data' + static readonly DELETE_DATA :string = 'data/%d' + + //TODO 首页 + static readonly OVERVIEW : string = 'overview' + static readonly OVERVIEW_SCRIPT : string = 'overview_scripts' + static readonly OVERVIEW_PRODUCT : string = 'overview_products' + +} + +/** + * 后台地址 + */ +export class AdminUrl { + static readonly LOGIN : string = "login"; // 登陆 +} \ No newline at end of file diff --git a/src/utils/login-request.ts b/src/utils/login-request.ts new file mode 100644 index 0000000..567ee6b --- /dev/null +++ b/src/utils/login-request.ts @@ -0,0 +1,52 @@ +import axios from 'axios'; +import store from "@/views/home/store/index"; + +// 创建axios实例,设置配置得默认值 +// const instance = axios.create({ +// baseUrl:'https://market.cebg.games', // 这里写接口的http地址, +// timeout: 5000, // 设置请求超时的默认值 +// }) +const instance = axios.create({ + baseURL: 'https://market.cebg.games', + timeout: 5000 +}) + +// 设置请求拦截器 +instance.interceptors.request.use( + config => { + // 判断当前是否有token,有则在请求头上加上token + if (store.getters.token) { + config.headers.Authorization = "Bearer " + store.getters.token + } + // console.log(config) + return config + }, + error => { + // 请求错误进行拦截并返回错误信息 + // console.log(error) + return Promise.reject(error) + } +) +// 设置响应拦截 +instance.interceptors.response.use( + response => { + const res = response.data + return res + }, + error => { + return Promise.reject(error) + } +) +export const Http_getNonce = (params) => + instance({ + url: 'https://market.cebg.games/webapp/index.php?c=Market&a=getNonce', + method: 'get', + params: params + }) + +export const Http_login = (data) => + instance({ + url: 'https://market.cebg.games/webapp/index.php?c=Market&a=auth', + method: 'get', + params: data + }) \ No newline at end of file diff --git a/src/views/admin/App.vue b/src/views/admin/App.vue new file mode 100644 index 0000000..29580e1 --- /dev/null +++ b/src/views/admin/App.vue @@ -0,0 +1,7 @@ + + + diff --git a/src/views/admin/Common/AdminParent.ts b/src/views/admin/Common/AdminParent.ts new file mode 100644 index 0000000..986635c --- /dev/null +++ b/src/views/admin/Common/AdminParent.ts @@ -0,0 +1,35 @@ +import Parent from "@/utils/Parnet"; + +export default class AdminParent extends Parent { + public success(content? : string, callback? : Function) { + this.$Message.success({ + content : content ? content : "操作成功", + onClose : () => { + this.$Message.destroy() + callback && callback(); + } + }) + } + + public error(content? : string, callback? : Function) { + this.$Message.error({ + content : content ? content : "操作失败", + onClose : () => { + this.$Message.destroy(); + callback && callback(); + } + }) + } + + doDeleteItem(title : string = "", callback : Function) { + this.$Modal.confirm({ + title : "删除", + content : title.length > 0 ? title : "确认要删除该数据吗?", + loading : true, + onOk : () => { + this.$Modal.remove(); + callback.call(this) + } + }) + } +} \ No newline at end of file diff --git a/src/views/admin/main.ts b/src/views/admin/main.ts new file mode 100644 index 0000000..3f99d4f --- /dev/null +++ b/src/views/admin/main.ts @@ -0,0 +1,30 @@ +import Vue from 'vue' +import App from "./App.vue" +import router from './router' +import store from './store' +import Http, { get, post } from "@/utils/Http"; +import "../../plugins/iview" +import "../../plugins/element"; +import "@/assets/style/tailwind.css" +const sprintf = require('sprintf-js'); + +Http.store = store; +Http.router = router; +Http.baseUri = '/rest/admin/' +Vue.prototype.$http = Http; +Vue.config.devtools = process.env.NODE_ENV !== 'production'; +Vue.config.silent = process.env.NODE_ENV === 'production'; +Vue.config.productionTip = process.env.NODE_ENV !== 'production'; +Vue.prototype.$isDev = process.env.NODE_ENV !== 'production'; + +Vue.prototype.$sprintf = sprintf.sprintf; + + +new Vue({ + router, + store, + render : h => h(App), + created() { + this.$http.vue = this; + } +}).$mount('#app') diff --git a/src/views/admin/pages/home/home.less b/src/views/admin/pages/home/home.less new file mode 100644 index 0000000..e69de29 diff --git a/src/views/admin/pages/home/home.ts b/src/views/admin/pages/home/home.ts new file mode 100644 index 0000000..a0914ea --- /dev/null +++ b/src/views/admin/pages/home/home.ts @@ -0,0 +1,17 @@ +import { Component, Vue } from 'vue-property-decorator' +import Url, {AdminUrl} from "@/utils/Url"; +import { + State, + Getter, + Action, + Mutation, +} from "vuex-class"; +import AdminParent from "@/views/admin/Common/AdminParent"; + + +@Component +export default class home extends AdminParent { + + public created() { + } +} \ No newline at end of file diff --git a/src/views/admin/pages/home/home.vue b/src/views/admin/pages/home/home.vue new file mode 100644 index 0000000..de7fd17 --- /dev/null +++ b/src/views/admin/pages/home/home.vue @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/src/views/admin/pages/layout/layout.less b/src/views/admin/pages/layout/layout.less new file mode 100644 index 0000000..e69de29 diff --git a/src/views/admin/pages/layout/layout.ts b/src/views/admin/pages/layout/layout.ts new file mode 100644 index 0000000..0b66659 --- /dev/null +++ b/src/views/admin/pages/layout/layout.ts @@ -0,0 +1,14 @@ +import { Component, Vue, Watch } from 'vue-property-decorator' +import { + State, + Getter, + Action, + Mutation, +} from "vuex-class"; +import AdminParent from "@/views/admin/Common/AdminParent"; + +@Component +export default class layout extends AdminParent { + + +} \ No newline at end of file diff --git a/src/views/admin/pages/layout/layout.vue b/src/views/admin/pages/layout/layout.vue new file mode 100644 index 0000000..a3bcfaf --- /dev/null +++ b/src/views/admin/pages/layout/layout.vue @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/src/views/admin/pages/layout/router.vue b/src/views/admin/pages/layout/router.vue new file mode 100644 index 0000000..e3bc8ac --- /dev/null +++ b/src/views/admin/pages/layout/router.vue @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/src/views/admin/pages/login/login.less b/src/views/admin/pages/login/login.less new file mode 100644 index 0000000..e69de29 diff --git a/src/views/admin/pages/login/login.ts b/src/views/admin/pages/login/login.ts new file mode 100644 index 0000000..d7ec2e3 --- /dev/null +++ b/src/views/admin/pages/login/login.ts @@ -0,0 +1,14 @@ +import { Component, Vue } from 'vue-property-decorator' +import Url from "@/utils/Url"; +import { + State, + Getter, + Action, + Mutation, +} from "vuex-class"; +import AdminParent from "@/views/admin/Common/AdminParent"; + +@Component +export default class Login extends AdminParent { + +} \ No newline at end of file diff --git a/src/views/admin/pages/login/login.vue b/src/views/admin/pages/login/login.vue new file mode 100644 index 0000000..7aaedd0 --- /dev/null +++ b/src/views/admin/pages/login/login.vue @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/src/views/admin/router/index.ts b/src/views/admin/router/index.ts new file mode 100644 index 0000000..6818f42 --- /dev/null +++ b/src/views/admin/router/index.ts @@ -0,0 +1,33 @@ +import Vue from 'vue' +import VueRouter, { RouteConfig } from 'vue-router' +import Routers from './router' +import { LoadingBar } from 'view-design' + +Vue.use(VueRouter) + +const routes : Array = [ + ...Routers +] + +const router = new VueRouter({ + base : "/admin/", + mode : "history", + routes, +}) + +function setTitle(title : string) : void { + window.document.title = title; +} + +router.beforeEach((to, from, next) => { + LoadingBar.start(); + let title : string = process.env.VUE_APP_TITLE; + setTitle(to.meta.title + '-' + title); + next(); +}) + +router.afterEach((to, from) => { + LoadingBar.finish(); +}) + +export default router diff --git a/src/views/admin/router/router.ts b/src/views/admin/router/router.ts new file mode 100644 index 0000000..b33967a --- /dev/null +++ b/src/views/admin/router/router.ts @@ -0,0 +1,27 @@ +import { RouteConfig } from "vue-router"; +import Layout from '../pages/layout/layout.vue' + +const routers : RouteConfig[] = [ + { + path : '/login', + name : 'login', + component : () => import(/* webpackChunkName: "admin-login" */ '@/views/admin/pages/login/login.vue'), + meta : {title : "登陆", isMenu : false} + }, { + path : "/", + name : 'layout', + component : Layout, + redirect : "/index", + meta : {title : "", isMenu : false}, + children : [{ + path : "index", + name : "index", + meta : {title : "首页", isMenu : true, icon : "", path : "/index"}, + // redirect : "/index/dashboard", + component : () => import(/* webpackChunkName: "admin-login" */ '@/views/admin/pages/home/home.vue'), + + },] + } +] + +export default routers \ No newline at end of file diff --git a/src/views/admin/store/index.ts b/src/views/admin/store/index.ts new file mode 100644 index 0000000..cc8e551 --- /dev/null +++ b/src/views/admin/store/index.ts @@ -0,0 +1,37 @@ +import Vue from 'vue' +import Vuex from 'vuex' +import user, {UserState} from "@/views/admin/store/user"; +import createPersistedState from 'vuex-persistedstate' + +Vue.use(Vuex) + +interface StateInterface { + user : UserState, +} + +const localStorage : any = window.localStorage; + +export default new Vuex.Store({ + getters : { + token : (state : StateInterface) => state.user.token, + }, + modules : { + user, + }, + plugins : [ + createPersistedState({ + key : 'admin', + storage : { + getItem : (key : string) => { + return localStorage.getItem(key); + }, + setItem : (key : string, value : any) => { + return localStorage.setItem(key, value); + }, + removeItem : (key : string) => { + return localStorage.removeItem(key); + } + } + }) + ] +}) diff --git a/src/views/admin/store/types.ts b/src/views/admin/store/types.ts new file mode 100644 index 0000000..c35b219 --- /dev/null +++ b/src/views/admin/store/types.ts @@ -0,0 +1,6 @@ +export default class Types { + static readonly SET_USER_INFO : string = 'SET_USER_INFO'; + static readonly SET_USER_TOKEN : string = 'SET_USER_TOKEN'; + static readonly SET_LANG : string = 'SET_LANG'; + static readonly SET_SITE_INFO : string = "SET_SITE_INFO"; +} \ No newline at end of file diff --git a/src/views/admin/store/user.ts b/src/views/admin/store/user.ts new file mode 100644 index 0000000..5dd889d --- /dev/null +++ b/src/views/admin/store/user.ts @@ -0,0 +1,41 @@ +import {ActionContext} from "vuex"; +import Http, { ResponseErrorInterface, ResponseInterface } from "@/utils/Http"; +import {AdminUrl} from "@/utils/Url"; +import Types from "@/views/admin/store/types"; +import { User } from "@/interface/User"; + +const sprintf = require('sprintf-js'); + +export interface UserState { + user : User, + token : string | undefined +} + +export default { + name : "user", + namespace : true, + state : { + user : {}, + token : undefined + }, + actions : { + Login(context : ActionContext, form : any) { + return Http.post(AdminUrl.LOGIN, form).then((res : ResponseInterface) => { + console.log(res); + context.commit(Types.SET_USER_INFO, res.data); + context.commit(Types.SET_USER_TOKEN, res.data.access_token); + return Promise.resolve(res.data); + }).catch((err : ResponseErrorInterface) => { + return Promise.reject(err); + }) + }, + }, + mutations : { + [Types.SET_USER_INFO](state : UserState, user : User) { + state.user = user; + }, + [Types.SET_USER_TOKEN](state : UserState, token : string) { + state.token = token; + } + } +} \ No newline at end of file diff --git a/src/views/admin/types/Types.ts b/src/views/admin/types/Types.ts new file mode 100644 index 0000000..839f285 --- /dev/null +++ b/src/views/admin/types/Types.ts @@ -0,0 +1,24 @@ +export interface MetaInterface { + title : string, + isMenu : boolean, + icon : string, + path : string, + isHide : boolean +} + +export interface MenuInterface { + id? : number, + title : string, + icon : string, + name : string, + path : string, + isHide : boolean, + children : MenuInterface[] | any[] +} + + +export enum ServerRetEnum { + SUCCESS, + NO_PERMISSION = 1001, + NOT_DATA = 1056, +} diff --git a/src/views/home/App.vue b/src/views/home/App.vue new file mode 100644 index 0000000..9721845 --- /dev/null +++ b/src/views/home/App.vue @@ -0,0 +1,9 @@ + + + + + diff --git a/src/views/home/Common/HomeParent.ts b/src/views/home/Common/HomeParent.ts new file mode 100644 index 0000000..8ae8534 --- /dev/null +++ b/src/views/home/Common/HomeParent.ts @@ -0,0 +1,28 @@ +import AdminParent from "@/views/admin/Common/AdminParent"; +import { Route } from "vue-router"; +import { Location } from "vue-router/types/router"; +export interface BreadItem { + location: Location + route: Route + href: string + normalizedTo: Location + resolved: Route + name?: string +} + + +export default class HomeParent extends AdminParent { + + public loading: boolean = false; + public startLoading() : void { + this.loading = true; + } + public stopLoading() : void { + this.loading = false; + } + + + + + +} \ No newline at end of file diff --git a/src/views/home/Common/Types.ts b/src/views/home/Common/Types.ts new file mode 100644 index 0000000..e283acf --- /dev/null +++ b/src/views/home/Common/Types.ts @@ -0,0 +1,80 @@ +export interface MenuInterface { + name : string; + url : string; + pid ?: number; + state ?: number; + id? : number; + children? : MenuInterface[] +} + +export interface PowerInterface { + id? : number; + name : string; + pid : number; + controller : string; + action : string; + children? : PowerInterface[] +} + +export interface PowerGroupInterface { + id? : number; + name : string; +} + +export interface ProjectInterface { + name : string; + tapd_id : any; + id? : number; +} + +export interface ProjectItemInterface { + id? : number; + name : string; + type : string; +} + +export interface ProjectProductInterface { + id? : number; + name : string; + commit_message : string; + days : number; + download_times : number; + build_result?: string; + state ?: boolean; +} +export interface ProjectProductLogInterface { + id? : number; + userName : string; + addTime : any; + productName : string; + days : number; + content : string +} + +export interface ProductIpsInterface { + id? : number; + project_item_id ?: number; + server_ip :string; + created_at ?: string; + old_ip ?: string; +} + +export interface UserInterface { + id? : number; + username : string; + name : string; + openid : string; + deleted_at : string; + old_name ?: string; +} + +export interface ElTreeDataInterface { + id : number; + label : string; + children : ElTreeDataInterface[] +} + +export enum EVENT_TYPE { + UPDATE_BREAD_ITEM = "UPDATE_BREAD_ITEM", + UPDATE_MY_PROJECT = "UPDATE_MY_PROJECT" +} \ No newline at end of file diff --git a/src/views/home/Common/UiTypes.ts b/src/views/home/Common/UiTypes.ts new file mode 100644 index 0000000..0f09ed4 --- /dev/null +++ b/src/views/home/Common/UiTypes.ts @@ -0,0 +1,45 @@ +export interface DeviceInterface { + id ?: number; + name : string; + model : number; + version : number; + serial_number : number; + connect_url : string; + pic_url : string; + is_running ?: number; + platform : string; +} + +export interface ScriptInterface { + id ?: number; + name : string; + project_id : number; + project ?: string; + user_id ?: number; + user ?: string; + description : string; + path : string; + checkFile?:[] +} + +export interface CollectionInterface { + id ?: number; + name : string; + project_id : number; + user_id ?: number; + description : string; + script : [] + list?:object +} + +export interface TaskInterface { + id ?: number; + name : string; + project_id : number; + project ?: string; + user_id ?: number; + description : string; + execute_count ?: number; + script_number ?: number; + list ?: [] +} diff --git a/src/views/home/cebg.d.ts b/src/views/home/cebg.d.ts new file mode 100644 index 0000000..dba423d --- /dev/null +++ b/src/views/home/cebg.d.ts @@ -0,0 +1,6 @@ +interface Window { + ethereum: any + web3: any + celo: any +} +declare let window: Window diff --git a/src/views/home/components/DownLoading.vue b/src/views/home/components/DownLoading.vue new file mode 100644 index 0000000..8e49c98 --- /dev/null +++ b/src/views/home/components/DownLoading.vue @@ -0,0 +1,178 @@ + + + + + \ No newline at end of file diff --git a/src/views/home/components/PaoMaDeng.vue b/src/views/home/components/PaoMaDeng.vue new file mode 100644 index 0000000..f4cf5ec --- /dev/null +++ b/src/views/home/components/PaoMaDeng.vue @@ -0,0 +1,162 @@ + + + + + + \ No newline at end of file diff --git a/src/views/home/components/phoneCard.less b/src/views/home/components/phoneCard.less new file mode 100644 index 0000000..41d5ab6 --- /dev/null +++ b/src/views/home/components/phoneCard.less @@ -0,0 +1,86 @@ + +.device-item { + display: block; + float: left; + width: 220px; + padding: 5px 0; + margin-left: 40px; + margin-bottom: 20px; + border: 1px solid #e1e1e1; + font-weight: 500; + cursor: pointer; + background-color: white; +} +.top { + overflow: hidden; + border-bottom: 1px solid #eee; + padding: 5px 10px; + display: inline-block; + height: auto; + width: 100%; + .icon{ + width: 20px; + height: 20px; + float: left; + //margin-top: 3px; + font-size: 18px; + } + + + .button { + text-align: right; + //margin-left: 40px; + float: right; + // margin-top: -40px; + display: inline-block; + vertical-align: middle; + } + .link{ + font-size: 16px; + margin-left: 5px; + } + .status-color1{ + color: #eb4e3d; + } + .status-color2{ + color: #2d8cf0; + } +} + +.div-inline{ + display: inline-block; +} +.left { + float: left; + margin-left: 10px; + width: 20%; + +} +.title { + font-family: "PingFang SC", "Helvetica Neue", Helvetica, "Hiragino Sans GB", STHeitiSC-Light, "Microsoft YaHei", "微软雅黑", Arial, sans-serif; width: 100%; + // height: 66px; + + text-align: left; + margin-left: 20px; + margin-top: 10px; + margin-bottom: 10px; + // padding-left: 30px; + // margin-bottom: 30px; + font-size: 13px; + color: #333; + // line-height: 33px; +} + +.device-info { + float: left; + width: 65%; + margin-left: 20px; + margin-top: 10px; + margin-bottom: 50px; + white-space:nowrap; + overflow:hidden; + text-overflow:ellipsis; + word-wrap:break-word; + word-break:break-all; + font-size: 12px; +} diff --git a/src/views/home/components/phoneCard.ts b/src/views/home/components/phoneCard.ts new file mode 100644 index 0000000..6526b77 --- /dev/null +++ b/src/views/home/components/phoneCard.ts @@ -0,0 +1,40 @@ +import HomeParent from "@/views/home/Common/HomeParent"; +import {Component, Prop, Vue} from "vue-property-decorator"; +import SvgIcon from '@/components/SvgIcon.vue' + + +@Component({ + components : { + SvgIcon + } +}) +export default class phoneCard extends Vue { + name:string = 'phoneCard' + + @Prop({type : Array, default : () => []}) + dataList : Array; + + public edit(e) { + if (e.is_running == 1) { + this.$Message.error({ + content:'设备正在运行中,无法编辑' + }) + return + } + this.$emit('edit-device',e) + } + public del(e) { + this.$emit('del-device',e) + } + public exec(e) { + this.$emit('exec',e) + } + + public copyLink(e){ + this.$Notice.info({ + title:'链接地址', + desc:e.connect_url, + duration:3 + }) + } +} \ No newline at end of file diff --git a/src/views/home/components/phoneCard.vue b/src/views/home/components/phoneCard.vue new file mode 100644 index 0000000..7c79c02 --- /dev/null +++ b/src/views/home/components/phoneCard.vue @@ -0,0 +1,40 @@ + + + + + diff --git a/src/views/home/main-app.ts b/src/views/home/main-app.ts new file mode 100644 index 0000000..e836351 --- /dev/null +++ b/src/views/home/main-app.ts @@ -0,0 +1,17 @@ +import {Component, Mixins, Vue, Watch} from 'vue-property-decorator' +import Url, {AdminUrl} from "@/utils/Url"; +import { + State, + Getter, + Action, + Mutation, +} from "vuex-class"; +import HomeParent from "@/views/home/Common/HomeParent"; + +@Component +export default class MainApp extends HomeParent { + + public created() { + } + +} \ No newline at end of file diff --git a/src/views/home/main.ts b/src/views/home/main.ts new file mode 100644 index 0000000..925abfb --- /dev/null +++ b/src/views/home/main.ts @@ -0,0 +1,36 @@ +import Vue from 'vue' +import App from "./App.vue" +import router from './router/index' +import store from './store' +import Http, { get, post } from "@/utils/Http"; +import "../../plugins/iview" +import "../../plugins/element"; +import "@/assets/style/tailwind.css" +import * as _ from 'lodash' +const sprintf = require('sprintf-js'); + +import '@/icons' + +Http.store = store; +Http.router = router; +Http.baseUri = '/rest/' +Vue.prototype.$http = Http; +Vue.config.devtools = process.env.NODE_ENV !== 'production'; +// Vue.config.devtools = true; +Vue.config.silent = process.env.NODE_ENV === 'production'; +// Vue.config.silent = false; +Vue.config.productionTip = process.env.NODE_ENV !== 'production'; +Vue.prototype.$isDev = process.env.NODE_ENV !== 'production'; + +Vue.prototype.$sprintf = sprintf.sprintf; +Vue.prototype.$_ = _; +Vue.prototype.$bus = new Vue({}); + +new Vue({ + router, + store, + render : h => h(App), + created() { + this.$http.vue = this; + } +}).$mount('#app') diff --git a/src/views/home/pages/home/home.less b/src/views/home/pages/home/home.less new file mode 100644 index 0000000..95779f9 --- /dev/null +++ b/src/views/home/pages/home/home.less @@ -0,0 +1,87 @@ +.boxx{ + margin: 0 0 18px 20px; + font-size: 40px +} +.demo-split1{ + height: 450px; + +} +.demo-split-pane1{ + padding: 10px; + +} +.demo-split{ + height: 680px; + margin-top: 50px; + background-color: #FFFFFF; + +} +.demo-split-pane{ + padding: 10px; + //border: 1px solid red; +} +.demo-split-pane.no-padding{ + height: 680px; + padding: 0; +} +.tag{ + background-color: #374151; + color: white; + padding: 8px 100px 8px 20px; + +} + +.arrow1 { + padding: 15px 0; + margin-left: 20px; + &-wrap { + //margin: 0 auto; + width: 120px; + height: 34px; + background-image: linear-gradient(to right, #227190 , #63c7ed); + color: #FFFFFF; + line-height: 34px; + text-align: start; + position: relative; + &::after { + content: ''; + position: absolute; + right: -34px; + border: 17px solid transparent; + border-left: 17px solid #63c7ed; + } + } +} +.arrow2 { + padding: 15px 0; + margin-left: 20px; + &-wrap { + //margin: 0 auto; + width: 180px; + height: 34px; + background-image: linear-gradient(to right, #008B8B , #7FFFD4); + color: #FFFFFF; + line-height: 34px; + text-align: start; + position: relative; + &::after { + content: ''; + position: absolute; + right: -34px; + border: 17px solid transparent; + border-left: 17px solid #7FFFD4; + } + } +} + +.progress:hover:before{ + content:attr(content); + background:#585e6b; + color:#fff; + padding:.8em 1em; + position:absolute; + bottom: -35px; + border-radius: 5px; + margin-left:30%; + +} diff --git a/src/views/home/pages/home/home.ts b/src/views/home/pages/home/home.ts new file mode 100644 index 0000000..76ba243 --- /dev/null +++ b/src/views/home/pages/home/home.ts @@ -0,0 +1,192 @@ +import { Component, Mixins, Vue, Watch } from 'vue-property-decorator' +import Url from "@/utils/Url"; +import { + State, + Getter, + Action, + Mutation +} from "vuex-class"; +import HomeParent from "@/views/home/Common/HomeParent"; +import CustomPage from "@/components/page.vue"; +import SvgIcon from '@/components/SvgIcon.vue' +import * as echarts from 'echarts' + +@Component({ + components : { + SvgIcon + } +}) +export default class Home extends HomeParent { + name : string = 'home' + public scriptList :any = [{ script_name: undefined, date: undefined,content:undefined}]; + public projectList :any = [{ product_name: undefined, date: undefined}] + public myChart; + public cardProductData:object = { + product_count_trashed:undefined, + product_today:undefined, + day_rate:undefined, + day_icon:undefined, + week_rate:undefined, + week_icon:undefined, + } + public cardScriptData:object = { + script_week:undefined, + use_rate:undefined + } + public cardTaskData:object = { + day_rate:undefined, + task_count:undefined, + task_month_sum:undefined + } + public userNumber:number=0; + + public created() { + // this._getData() + // this._getScriptLog() + // this._getProductLog() + } + + + private createChart(data) { + this.myChart = echarts.init(this.$refs['myChart'] as HTMLCanvasElement); + this.myChart.setOption(this.option(data)); + this.myChart.on('updateAxisPointer', (event)=> { + const xAxisInfo = event.axesInfo[0]; + if (xAxisInfo) { + const dimension = xAxisInfo.value + 1; + this.myChart.setOption({ + series: { + id: 'pie', + label: { + formatter: '{b}: {@[' + dimension + ']} ({d}%)' + }, + encode: { + value: dimension, + tooltip: dimension + } + } + }); + } + }); + } + + + public shortcut(str){ + this._push({name:str}) + } + + private option(data) { + return { + legend: {}, + tooltip: { + trigger: 'axis', + showContent: false + }, + dataset: { + source: data + }, + xAxis: { type: 'category' }, + yAxis: { gridIndex: 0 }, + grid: { top: '55%' }, + series: [ + { + type: 'line', + smooth: true, + seriesLayoutBy: 'row', + emphasis: { focus: 'series' } + }, + { + type: 'line', + smooth: true, + seriesLayoutBy: 'row', + emphasis: { focus: 'series' } + }, + { + type: 'line', + smooth: true, + seriesLayoutBy: 'row', + emphasis: { focus: 'series' } + }, + { + type: 'line', + smooth: true, + seriesLayoutBy: 'row', + emphasis: { focus: 'series' } + }, + { + type: 'pie', + id: 'pie', + radius: '30%', + center: ['50%', '25%'], + emphasis: { + focus: 'self' + }, + label: { + formatter: '{b}: {@2012} ({d}%)' + }, + encode: { + itemName: 'product', + value: '2012', + tooltip: '2012' + } + } + ] + } + } + + private _getData():void{ + this.$http.get(Url.OVERVIEW).then((res)=>{ + this.createChart(res.data.recent_task_logs) + + this.cardProductData = this.alterProduct(res.data.card_data.product) + this.cardTaskData = this.alterTask(res.data.card_data.task) + this.cardScriptData = this.alterScript(res.data.card_data.script) + this.userNumber = res.data.card_data.user + + }).catch((msg:string)=>{ + this.error(msg) + }) + } + + private _getScriptLog():void{ + this.$http.get(Url.OVERVIEW_SCRIPT).then((res)=>{ + this.scriptList = res.data + }).catch((msg:string)=>{ + this.error(msg) + }) + } + + private _getProductLog():void{ + this.$http.get(Url.OVERVIEW_PRODUCT).then((res)=>{ + this.projectList = res.data + }).catch((msg:string)=>{ + this.error(msg) + }) + } + + private alterProduct(data):object{ + return { + product_count_trashed:data.product_count_trashed, + product_today:data.product_today, + day_rate:data.product_yesterday==0?'(昨日无构建)':Math.abs(Math.floor(((parseInt(data.product_today)-parseInt(data.product_yesterday)) / parseInt(data.product_yesterday ))*100))+'%', + day_icon:parseInt(data.product_today) > parseInt(data.product_yesterday)?1:2, + week_rate:Math.abs(Math.floor(((parseInt(data.product_current_week)-parseInt(data.product_last_week)) / parseInt(data.product_last_week ))*100))+'%', + week_icon:parseInt(data.product_current_week) > parseInt(data.product_last_week)?1:2, + } + } + + private alterTask(data):object{ + return { + task_count:data.task_count, + task_month_sum:data.task_month_sum, + day_rate:Math.abs(Math.floor((parseInt(data.task_end_sum)/ parseInt(data.task_logs_sum ))*100)) + } + } + + private alterScript(data):object{ + return { + script_week:data.script_week, + use_rate:Math.abs(Math.floor((parseInt(data.script_used)/ parseInt(data.script_count ))*100))+'%' + } + } +} \ No newline at end of file diff --git a/src/views/home/pages/home/home.vue b/src/views/home/pages/home/home.vue new file mode 100644 index 0000000..6aeca3d --- /dev/null +++ b/src/views/home/pages/home/home.vue @@ -0,0 +1,184 @@ + + + + \ No newline at end of file diff --git a/src/views/home/pages/layout/layout.less b/src/views/home/pages/layout/layout.less new file mode 100644 index 0000000..449ee9d --- /dev/null +++ b/src/views/home/pages/layout/layout.less @@ -0,0 +1,15 @@ + +.item-menu { + color: hsla(0,0%,100%,.7); +} +.layout{ + border: 1px solid #d7dde4; + background: #f5f7f9; + position: relative; + border-radius: 4px; + overflow: hidden; +} + + + + diff --git a/src/views/home/pages/layout/layout.ts b/src/views/home/pages/layout/layout.ts new file mode 100644 index 0000000..715caad --- /dev/null +++ b/src/views/home/pages/layout/layout.ts @@ -0,0 +1,154 @@ +import { Component, Mixins, Vue, Watch } from 'vue-property-decorator' +import Url, { AdminUrl } from "@/utils/Url"; +import { + State, + Getter, + Action, + Mutation, +} from "vuex-class"; +import Types from "@/views/admin/store/types"; +import HomeParent, { BreadItem } from "@/views/home/Common/HomeParent"; +import { EVENT_TYPE, MenuInterface, ProjectInterface } from "@/views/home/Common/Types"; +import { Route, RouteRecord } from "vue-router"; +import { Menu } from "view-design"; + + + +@Component +export default class Layout extends HomeParent { + + public siteLogo: object = {} + public siteMarquee: object[] = []; + public visible: boolean = false; + public myMenus: MenuInterface[] = []; + public routes: BreadItem[] = []; + public myProject: ProjectInterface[] = []; + + + public created() { + this._getMyMenu() + // this._getMyProject() + // this.$bus.$on(EVENT_TYPE.UPDATE_BREAD_ITEM, this._updateRoutes.bind(this)); + // this.$bus.$on(EVENT_TYPE.UPDATE_MY_PROJECT, this._getMyProject.bind(this)); + } + + @Watch("$route", {immediate: true, deep: true}) + private _watchRoute(route: Route): void { + this.routes.length = 0; + route.matched.forEach((item: RouteRecord) => { + let ret: BreadItem = this.$router.resolve({name: item.name, params: route.params}); + ret.name = item.meta.title; + this.routes.push(ret); + // console.log(ret); + }); + } + + + public get isLogin() { + return this.$store.state.user.token.length > 0; + } + + public onDropdownEvent(name: string): void { + switch (name) { + case "logout" : this.logout();break; + case "updateName" : this.updateName();break; + } + } + + public logout() { + this.$http.get(Url.LOGOUT).then(res => { + this._push('/login') + }); + } + + public updateName(){ + let self = this; + this.$Modal.confirm({ + title:"修改昵称", + render: function(h) { + return h('Input', { + props: { + value: self.$store.getters.user.name, + autofocus: true, + placeholder: 'Please enter your name...' + }, + on: { + input: (val) => { + this.value = val; + } + } + }) + }, + onOk :function() { + self.onVisibleChange(true) + if (!this.value || this.value == self.$store.getters.user.name || this.value == '') { + return; + } + let data = {name:this.value}; + self.$http.put(Url.UPDATE_MY_NAME,data).then(res=>{ + self.success('修改成功',()=>{ + self.$store.getters.user.name = res.data.name + let obj = JSON.parse(window.localStorage.getItem('home')); + obj.user.user.name = res.data.name + window.localStorage.setItem('home',JSON.stringify(obj)); + }) + }).catch((err)=>{ + self.error(err) + }) + } + }) + } + + /** + * 跳转页面时关闭dropdown + * @param name + */ + public onMenuChange(name): void { + this.visible = false; + } + + /** + * 切换dropdown显示状态 + * @param st + */ + public onVisibleChange(st: boolean) : void { + this.visible = !this.visible; + } + + + private _getMyMenu() { + this.myMenus = [ + {id:1,name:'用户管理',url:'/user',children:[ + {id:2,name:'用户列表',url:'/user/list',children:[]}, + {id:3,name:'用户权限',url:'/user/node',children:[]}, + {id:4,name:'用户菜单',url:'/user/menu',children:[]}, + ]}, + {id:5,name:'表格',url:'/table',children:[ + {id:6,name:'普通表格',url:'/table/base',children:[]}, + {id:7,name:'复杂表格',url:'/table/complex',children:[]}, + ]} + ] + } + + private _getMyProject() { + this.$http.get(Url.MY_PROJECT).then(res => { + this.myProject = res.data; + }); + } + + private _parseTreeData(list: any[]) { + return list.map((item: any) => { + if (item.children.length > 0) { + item._showChildren = true; + item.children = this._parseTreeData(item.children); + } + return item + }); + } + + private _updateRoutes(name: string, idx: number): void { + if (this.routes.length >= idx - 1) { + this.routes[idx].name = name; + } + } +} \ No newline at end of file diff --git a/src/views/home/pages/layout/layout.vue b/src/views/home/pages/layout/layout.vue new file mode 100644 index 0000000..0c29733 --- /dev/null +++ b/src/views/home/pages/layout/layout.vue @@ -0,0 +1,54 @@ + + + + \ No newline at end of file diff --git a/src/views/home/pages/layout/router.vue b/src/views/home/pages/layout/router.vue new file mode 100644 index 0000000..e3bc8ac --- /dev/null +++ b/src/views/home/pages/layout/router.vue @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/src/views/home/pages/login/login.less b/src/views/home/pages/login/login.less new file mode 100644 index 0000000..e050be5 --- /dev/null +++ b/src/views/home/pages/login/login.less @@ -0,0 +1,102 @@ +.login { + height: 100%; + width: 100%; + background: url(../../../../assets/image/loginBg.jpg) no-repeat center center; + background-size: 100% 100%; + overflow: hidden; +} +.loginBox { + height: 455px; + width: 550px; + margin: 0 auto; + position: relative; + top: 50%; + margin-top: -287px; +} +.loginH2 { + font-size: 38px; + color: #fff; + text-align: center; +} +.loginCon { + margin-top: 30px; + background: #eee; + border-radius: 4px; + .titleDiv { + padding: 0 28px; + background: #fff; + position: relative; + height: 120px; + border-radius: 4px 4px 0 0; + h3 { + font-size: 22px; + color: #555; + font-weight: initial; + padding-top: 23px; + } + p { + font-size: 16px; + color: #888; + padding-top: 12px; + } + i { + font-size: 65px; + color: #ddd; + position: absolute; + right: 27px; + top: 29px; + } + } + .el-form { + padding: 25px 25px 30px 25px; + background: #eee; + border-radius: 0 0 4px 4px; + } +} +.loginBtn { + width: 100%; + background: #19b9e7; +} +.slideShadow { + position: fixed; + z-index: 999; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.6); +} +.slideSty { + position: absolute; + width: 380px; + height: 311px; + background: #e8e8e8; + border: 1px solid #dcdcdc; + left: 50%; + top: 50%; + margin-left: -188px; + margin-top: -176px; + z-index: 99; + border-radius: 5px; +} +.iconBtn { + padding: 9px 0 0 19px; + color: #5f5f5f; + border-top: 1px solid #d8d8d8; + margin-top: 17px; + i { + font-size: 22px; + cursor: pointer; + } + i:last-child { + margin-left: 7px; + } +} + .slideSty .slide-verify { + margin: 13px auto 0 auto; + width: 350px !important; + } +.slideSty .slide-verify-slider { + width: 100% !important; +} +.slideSty .slide-verify-refresh-icon { + display: none; +} \ No newline at end of file diff --git a/src/views/home/pages/login/login.ts b/src/views/home/pages/login/login.ts new file mode 100644 index 0000000..a736267 --- /dev/null +++ b/src/views/home/pages/login/login.ts @@ -0,0 +1,227 @@ +import { Component, Mixins, Vue, Watch } from 'vue-property-decorator' +import Url, { AdminUrl } from "@/utils/Url"; +import { + State, + Getter, + Action, + Mutation, +} from "vuex-class"; +import { Form } from "view-design"; +import "@/assets/js/wwlogin"; +import HomeParent from "@/views/home/Common/HomeParent"; +import Types from "@/views/admin/store/types"; +import SvgIcon from '@/components/SvgIcon.vue' +import { Message } from 'element-ui'; +import ChainManager from '@/metamask/ChainManager' +import Web3 from 'web3' +import {Http_getNonce, Http_login} from "@/utils/login-request"; +import store from "@/views/home/store"; +import {ActionContext} from "vuex"; +import {UserState} from "@/views/home/store/user"; + +interface Window { + ethereum: any + web3: any + celo: any +} +declare let window: Window + +@Component({ + components : { + SvgIcon + } +}) +export default class Login extends HomeParent { + @Action('Login') Login; + public context : ActionContext + public provider:any + public web3:Web3 + public chainId:number + public account:string + public nonce:string + + public async btn(){ + if (!this.$store.getters.step){ + try { + await this.connect() + await this.checkNance() + } catch (err) { + Message({ + message: err.message, + type: 'error', + duration: 5 * 1000 + }) + Promise.reject(err) + } + } + } + + + + + + public get accessToken(): string { + let token: string = this.$route.query.access_token as string; + return token; + } + + public rules: object = { + username: [{required: true, trigger: 'blur', message: "请输入登录名"}], + password: [{required: true, trigger: 'blur', message: "请输入登录密码"}], + } + + public created() { + // if(!this.accessToken) { + // this._getData(); + // } + } + + private _getData(): void { + this.$http.get(Url.WORK_LOGIN).then(res => { + window["WwLogin"]({ + id: "loginPage", + goto: res.data + }) + }) + } + + // public doLogin(): void { + // let form: Form = this.$refs["login"] as Form; + // form.validate((valid: boolean) => { + // if (!valid) { + // return; + // } + // // this.Login(this.form).then((user) => { + // // this.goPage("home"); + // // }).catch((msg: string) => { + // // this.error(msg); + // // }) + // }); + // } + private async login( account, chainId, nonce) { + nonce += '' + const tips = 'This signature is only used for verify your account' + const signMsg = { + tips, + nonce + } + const EIP721_DOMAIN_DATA = [ + { name: 'name', type: 'string' }, + { name: 'version', type: 'string' } + ] + + const signObj = { + types: { + EIP712Domain: EIP721_DOMAIN_DATA, + set: [ + { name: 'tips', type: 'string' }, + { name: 'nonce', type: 'string' } + ] + }, + primaryType: 'set', + domain: { + name: 'Auth', + version: '1' + }, + message: signMsg + } + const signature = await this.signData(signObj, account) + const authData = { + account, + nonce, + signature, + tips, + net_id: chainId + } + const res:any = await Http_login(authData) + if (!res.errcode && res.token) { + store.commit('set_token',res.token) + console.log(store.getters) + } + } + private async checkNance() { + try { + let nonce = store.getters.nonce + if (!nonce) { + let params = {account:this.account, net_id:this.chainId} + const res:any = await Http_getNonce(params); + store.commit('set_nonce',res.nonce) + this.nonce = res.nonce + } + this.login(this.account,this.chainId,this.nonce) + } catch (err) { + Promise.reject(err) + } + } + private async connect(){ + if (!this.hasMetamask()) { + Message({ + message: '请先安装MetaMask插件', + type: 'error', + duration: 5 * 1000 + }) + return + } + this.provider = await this.connectMetaMask() + if (!this.provider) { + return + } + + this.web3 = new Web3(this.provider) + this.chainId = await this.web3.eth.getChainId() + const accounts = await this.web3.eth.getAccounts() + if (accounts && accounts.length > 0) { + this.account =accounts[0]; + } + } + private async connectMetaMask() { + let provider = window.ethereum + try { + await provider.request({ method: 'eth_requestAccounts' }) + } catch (error) { + if (error.code === -32002) { + throw new Error('MeatMask not login, Open MeatMask and login first') + } else { + throw new Error('User Rejected') + } + } + return provider + } + + private hasMetamask():boolean { + if (typeof window.ethereum !== 'undefined') { + return true; + }else { + return false; + } + } + private async signData(signObj: any, signer: string) { + const msgParams = JSON.stringify(signObj) + const from = signer + const params = [from, msgParams] + const result: any = await this.sendCmd({ + method: 'eth_signTypedData_v4', + params, + from + }) + return result.result + } + private async sendCmd({ method, params, from }:any) { + return new Promise((resolve, reject) => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + this.web3.currentProvider.sendAsync({ + method, + params, + from + }, async function(err: any, result: any) { + if (err) { + reject && reject(err) + return + } + resolve && resolve(result) + }) + }) + } + +} \ No newline at end of file diff --git a/src/views/home/pages/login/login.vue b/src/views/home/pages/login/login.vue new file mode 100644 index 0000000..b1ede5f --- /dev/null +++ b/src/views/home/pages/login/login.vue @@ -0,0 +1,14 @@ + + + + \ No newline at end of file diff --git a/src/views/home/router/index.ts b/src/views/home/router/index.ts new file mode 100644 index 0000000..4605019 --- /dev/null +++ b/src/views/home/router/index.ts @@ -0,0 +1,41 @@ +import Vue from 'vue' +import VueRouter, { RouteConfig } from 'vue-router' +import Routers from './router' +import Store from '../store' +import { LoadingBar } from 'view-design' +import Types from "@/views/home/store/types"; + +Vue.use(VueRouter) + +const routes : Array = [ + ...Routers +] + +const router = new VueRouter({ + base : "/dashboard/", + mode : "history", + routes, +}) + +function setTitle(title : string) : void { + window.document.title = title; +} + +router.beforeEach((to, from, next) => { + LoadingBar.start(); + let title : string = process.env.VUE_APP_TITLE; + setTitle(to.meta.title + '-' + title); + let token : string = to.query.access_token as string; + if (token) { + Store.commit(Types.SET_USER_TOKEN, token); + next({name : to.name === 'login'? 'home' : to.name}) + return; + } + next(); +}) + +router.afterEach((to, from) => { + LoadingBar.finish(); +}) + +export default router diff --git a/src/views/home/router/router.ts b/src/views/home/router/router.ts new file mode 100644 index 0000000..cbc744a --- /dev/null +++ b/src/views/home/router/router.ts @@ -0,0 +1,23 @@ +import { RouteConfig } from "vue-router"; +import Layout from '../pages/layout/layout.vue' + +const routers: RouteConfig[] = [{ + path: "/", + name: 'layout', + component: Layout, + redirect: "/home", + meta: {title: "首页", isMenu: false}, + children: [{ + path: "home", + name: 'home', + meta: {title: "dashboard", isMenu: false}, + component: () => import(/* webpackChunkName: "home-home" */ '@/views/home/pages/home/home.vue'), + }] +}, { + path: "/login", + name: 'login', + meta: {title: "", isMenu: false}, + component: () => import(/* webpackChunkName: "home-login" */ '@/views/home/pages/login/login.vue'), +}]; + +export default routers; \ No newline at end of file diff --git a/src/views/home/store/index.ts b/src/views/home/store/index.ts new file mode 100644 index 0000000..d629e56 --- /dev/null +++ b/src/views/home/store/index.ts @@ -0,0 +1,54 @@ +import Vue from 'vue' +import Vuex from 'vuex' +import user, { UserState } from "@/views/home/store/user"; +import createPersistedState from 'vuex-persistedstate' + +Vue.use(Vuex) + +interface StateInterface { + user : UserState, +} + +const localStorage : any = window.localStorage; + +export default new Vuex.Store({ + getters : { + token : (state : StateInterface) => state.user.token, + user : (state : StateInterface) => state.user.user, + step:state => state.step, + nonce:state => state.nonce + }, + modules : { + user + }, + plugins : [ + createPersistedState({ + key : 'home', + storage : { + getItem : (key : string) => { + return localStorage.getItem(key); + }, + setItem : (key : string, value : any) => { + return localStorage.setItem(key, value); + }, + removeItem : (key : string) => { + return localStorage.removeItem(key); + } + } + }) + ], + mutations: { + set_nonce(state, nonce) { + state.nonce = nonce + // sessionStorage.nonce = nonce + }, + set_token(state, token) { + state.token = token + sessionStorage.token = token + }, + del_token(state) { + state.token = '' + sessionStorage.removeItem('token') + } + } +}) diff --git a/src/views/home/store/types.ts b/src/views/home/store/types.ts new file mode 100644 index 0000000..44b307e --- /dev/null +++ b/src/views/home/store/types.ts @@ -0,0 +1,9 @@ +export default class Types { + static readonly SET_USER_INFO : string = 'SET_USER_INFO'; + static readonly SET_USER_TOKEN : string = 'SET_USER_TOKEN'; + static readonly SET_LANG : string = 'SET_LANG'; + static readonly SET_SITE_INFO : string = "SET_SITE_INFO"; + + static readonly SET_USER_CURRENCY : string = 'SET_USER_CURRENCY'; + static readonly UPDATE_USER_CREDIT : string = 'UPDATE_USER_CREDIT' +} \ No newline at end of file diff --git a/src/views/home/store/user.ts b/src/views/home/store/user.ts new file mode 100644 index 0000000..ac03407 --- /dev/null +++ b/src/views/home/store/user.ts @@ -0,0 +1,46 @@ +import {ActionContext} from "vuex"; +import Http, { ResponseErrorInterface, ResponseInterface } from "@/utils/Http"; +import Url from "@/utils/Url"; +import Types from "@/views/home/store/types"; +import { UserInterface } from "@/views/home/Common/Types"; + +const sprintf = require('sprintf-js'); + +export interface UserState { + user : UserInterface, + token : string | undefined, + isLogin : boolean, +} + +export default { + name : "user", + namespace : true, + state : { + user : {}, + token : undefined, + isLogin : false + }, + actions : { + Login(context : ActionContext, form : any) { + return Http.post(Url.LOGIN, form).then((res : ResponseInterface) => { + context.commit(Types.SET_USER_INFO, res.data); + context.commit(Types.SET_USER_TOKEN, res.data.access_token); + return Promise.resolve(res.data); + }).catch((err : string) => { + return Promise.reject(err); + }) + }, + getMenus() { + // return Http.get() + } + }, + mutations : { + [Types.SET_USER_INFO](state : UserState, user : UserInterface) { + state.user = user; + }, + [Types.SET_USER_TOKEN](state : UserState, token : string) { + state.token = token; + state.isLogin = token.length > 0; + }, + } +} \ No newline at end of file diff --git a/src/views/wap/App.vue b/src/views/wap/App.vue new file mode 100644 index 0000000..eaefdf3 --- /dev/null +++ b/src/views/wap/App.vue @@ -0,0 +1,12 @@ + + + \ No newline at end of file diff --git a/src/views/wap/Common/WapParent.ts b/src/views/wap/Common/WapParent.ts new file mode 100644 index 0000000..2993d22 --- /dev/null +++ b/src/views/wap/Common/WapParent.ts @@ -0,0 +1,20 @@ +import Parent from "@/utils/Parnet"; + +export default class WapParent extends Parent { + public success(content? : string, callback? : Function) { + this.$toast.success( { + message: content ? content : "操作成功", + onClose : () => { + callback && callback(); + } + }) + } + public error(content? : string, callback? : Function){ + this.$toast.fail( { + message: content ? content : "操作成功", + onClose : () => { + callback && callback(); + } + }) + } +} \ No newline at end of file diff --git a/src/views/wap/assets/css/main.less b/src/views/wap/assets/css/main.less new file mode 100644 index 0000000..e69de29 diff --git a/src/views/wap/main.ts b/src/views/wap/main.ts new file mode 100644 index 0000000..d99a465 --- /dev/null +++ b/src/views/wap/main.ts @@ -0,0 +1,29 @@ +import Vue from 'vue' +import App from "./App.vue" +import store from '../home/store' +import Http, { get, post } from "@/utils/Http"; +import router from "@/views/wap/router"; +const sprintf = require('sprintf-js'); +import "../../plugins/vant"; +Http.store = store; +Http.router = router; +Http.baseUri = '/rest/api/' + +Vue.prototype.$http = Http; +Vue.config.devtools = process.env.NODE_ENV !== 'production'; +// Vue.config.devtools = true; +Vue.config.silent = process.env.NODE_ENV === 'production'; +// Vue.config.silent = false; +Vue.config.productionTip = process.env.NODE_ENV !== 'production'; +Vue.prototype.$isDev = process.env.NODE_ENV !== 'production'; + +Vue.prototype.$sprintf = sprintf.sprintf; + +new Vue({ + router, + store, + render : h => h(App), + created() { + this.$http.vue = this; + } +}).$mount('#app') \ No newline at end of file diff --git a/src/views/wap/pages/home/home.less b/src/views/wap/pages/home/home.less new file mode 100644 index 0000000..e69de29 diff --git a/src/views/wap/pages/home/home.ts b/src/views/wap/pages/home/home.ts new file mode 100644 index 0000000..3cc264e --- /dev/null +++ b/src/views/wap/pages/home/home.ts @@ -0,0 +1,17 @@ +import {Component, Mixins, Vue, Watch} from 'vue-property-decorator' +import Url, {AdminUrl} from "@/utils/Url"; +import { + State, + Getter, + Action, + Mutation, +} from "vuex-class"; + +import WapParent from "@/views/wap/Common/WapParent"; + +@Component +export default class Home extends WapParent { + public created() : void { + } + +} \ No newline at end of file diff --git a/src/views/wap/pages/home/home.vue b/src/views/wap/pages/home/home.vue new file mode 100644 index 0000000..51eb605 --- /dev/null +++ b/src/views/wap/pages/home/home.vue @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/src/views/wap/pages/layout/layout.ts b/src/views/wap/pages/layout/layout.ts new file mode 100644 index 0000000..20289a8 --- /dev/null +++ b/src/views/wap/pages/layout/layout.ts @@ -0,0 +1,36 @@ +import {Component, Mixins, Vue, Watch} from 'vue-property-decorator' +import Url, {AdminUrl} from "@/utils/Url"; +import { + State, + Getter, + Action, + Mutation, +} from "vuex-class"; +import WapParent from "@/views/wap/Common/WapParent"; + +@Component +export default class Layout extends WapParent { + + public active : number = 0 + public showMenu: boolean = false; + + public created() { + console.log(this.$router); + } + + public showMore() : void { + this.showMenu = !this.showMenu; + } + + + public go(name) : void { + this.showMenu = false; + this.goPage(name); + } + + public onClickLeft() : void { + } + + public onClickRight() : void { + } +} \ No newline at end of file diff --git a/src/views/wap/pages/layout/layout.vue b/src/views/wap/pages/layout/layout.vue new file mode 100644 index 0000000..20b1b6d --- /dev/null +++ b/src/views/wap/pages/layout/layout.vue @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/src/views/wap/pages/login/login.ts b/src/views/wap/pages/login/login.ts new file mode 100644 index 0000000..93bc8e3 --- /dev/null +++ b/src/views/wap/pages/login/login.ts @@ -0,0 +1,27 @@ +import {Component, Mixins, Vue, Watch} from 'vue-property-decorator' +import Url, {AdminUrl} from "@/utils/Url"; +import { + State, + Getter, + Action, + Mutation, +} from "vuex-class"; +import {Route} from "vue-router"; +import WapParent from "@/views/wap/Common/WapParent"; +Component.registerHooks([ + 'beforeRouteEnter', + 'beforeRouteLeave', + 'beforeRouteUpdate', +]); +@Component +export default class Login extends WapParent { + beforeRouteEnter(to, from, next) { + next(vm => { + vm.lastRoute = from; + }); + } + public lastRoute : Route ; + public created() { + } + +} \ No newline at end of file diff --git a/src/views/wap/pages/login/login.vue b/src/views/wap/pages/login/login.vue new file mode 100644 index 0000000..0a7db2d --- /dev/null +++ b/src/views/wap/pages/login/login.vue @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/src/views/wap/router/index.ts b/src/views/wap/router/index.ts new file mode 100644 index 0000000..c25171d --- /dev/null +++ b/src/views/wap/router/index.ts @@ -0,0 +1,41 @@ +import Vue from 'vue' +import VueRouter, { RouteConfig } from 'vue-router' +import Routers from './router' +import Store from '../../home/store' +import Types from "@/views/home/store/types"; +import "../../../plugins/vant" + +Vue.use(VueRouter) + +const routes : Array = [ + ...Routers +] + +const router = new VueRouter({ + base : "/wap", + mode : "history", + routes, +}) + +function setTitle(title : string) : void { + window.document.title = title; +} + +router.beforeEach((to, from, next) => { + let title : string = process.env.VUE_APP_TITLE; + setTitle(to.meta.title + '-' + title); + let token : string = to.query.access_token as string; + if (token) { + Store.commit(Types.SET_USER_TOKEN, token); + let query : any = to.query; + delete query.access_token; + next({name : to.name, params : to.params, query : query}) + return; + } + next(); +}) + +router.afterEach((to, from) => { +}) + +export default router diff --git a/src/views/wap/router/router.ts b/src/views/wap/router/router.ts new file mode 100644 index 0000000..b3cf6d9 --- /dev/null +++ b/src/views/wap/router/router.ts @@ -0,0 +1,23 @@ +import {RouteConfig} from "vue-router"; +import Layout from '../pages/layout/layout.vue' + +const routers : RouteConfig[] = [{ + path : "/", + name : "/", + component : Layout, + redirect : "/home", + children : [{ + path : "home", + name : "home", + meta : {title : "首页", isMenu : false}, + component : () => import(/* webpackChunkName: "wap-home" */ '@/views/wap/pages/home/home.vue'), + }, { + path : "login", + name : "login", + meta : {title : "", isMenu : false}, + component : () => import(/* webpackChunkName: "wap-login" */ '@/views/wap/pages/login/login.vue'), + },] +}]; + + +export default routers; \ No newline at end of file diff --git a/tailwind.config.js b/tailwind.config.js new file mode 100644 index 0000000..df81c71 --- /dev/null +++ b/tailwind.config.js @@ -0,0 +1,974 @@ +const colors = require('tailwindcss/colors') + +module.exports = { + purge: [ + './src/**/*.html', + './src/**/*.{vue,js,ts,jsx,tsx}', + ], + presets: [], + darkMode: "class", // or 'media' or 'class' + theme: { + screens: { + sm: '640px', + md: '768px', + lg: '1024px', + xl: '1280px', + '2xl': '1536px', + }, + colors: { + transparent: 'transparent', + current: 'currentColor', + + black: colors.black, + white: colors.white, + gray: colors.coolGray, + red: colors.red, + yellow: colors.amber, + green: colors.emerald, + blue: colors.blue, + indigo: colors.indigo, + purple: colors.violet, + pink: colors.pink, + }, + spacing: { + px: '1px', + 0: '0px', + 0.5: '0.125rem', + 1: '0.25rem', + 1.5: '0.375rem', + 2: '0.5rem', + 2.5: '0.625rem', + 3: '0.75rem', + 3.5: '0.875rem', + 4: '1rem', + 5: '1.25rem', + 6: '1.5rem', + 7: '1.75rem', + 8: '2rem', + 9: '2.25rem', + 10: '2.5rem', + 11: '2.75rem', + 12: '3rem', + 14: '3.5rem', + 16: '4rem', + 20: '5rem', + 24: '6rem', + 28: '7rem', + 32: '8rem', + 36: '9rem', + 40: '10rem', + 44: '11rem', + 48: '12rem', + 52: '13rem', + 56: '14rem', + 60: '15rem', + 64: '16rem', + 72: '18rem', + 80: '20rem', + 96: '24rem', + }, + animation: { + none: 'none', + spin: 'spin 1s linear infinite', + ping: 'ping 1s cubic-bezier(0, 0, 0.2, 1) infinite', + pulse: 'pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite', + bounce: 'bounce 1s infinite', + }, + backdropBlur: (theme) => theme('blur'), + backdropBrightness: (theme) => theme('brightness'), + backdropContrast: (theme) => theme('contrast'), + backdropGrayscale: (theme) => theme('grayscale'), + backdropHueRotate: (theme) => theme('hueRotate'), + backdropInvert: (theme) => theme('invert'), + backdropOpacity: (theme) => theme('opacity'), + backdropSaturate: (theme) => theme('saturate'), + backdropSepia: (theme) => theme('sepia'), + backgroundColor: (theme) => theme('colors'), + backgroundImage: { + none: 'none', + 'gradient-to-t': 'linear-gradient(to top, var(--tw-gradient-stops))', + 'gradient-to-tr': 'linear-gradient(to top right, var(--tw-gradient-stops))', + 'gradient-to-r': 'linear-gradient(to right, var(--tw-gradient-stops))', + 'gradient-to-br': 'linear-gradient(to bottom right, var(--tw-gradient-stops))', + 'gradient-to-b': 'linear-gradient(to bottom, var(--tw-gradient-stops))', + 'gradient-to-bl': 'linear-gradient(to bottom left, var(--tw-gradient-stops))', + 'gradient-to-l': 'linear-gradient(to left, var(--tw-gradient-stops))', + 'gradient-to-tl': 'linear-gradient(to top left, var(--tw-gradient-stops))', + }, + backgroundOpacity: (theme) => theme('opacity'), + backgroundPosition: { + bottom: 'bottom', + center: 'center', + left: 'left', + 'left-bottom': 'left bottom', + 'left-top': 'left top', + right: 'right', + 'right-bottom': 'right bottom', + 'right-top': 'right top', + top: 'top', + }, + backgroundSize: { + auto: 'auto', + cover: 'cover', + contain: 'contain', + }, + blur: { + 0: '0', + none: '0', + sm: '4px', + DEFAULT: '8px', + md: '12px', + lg: '16px', + xl: '24px', + '2xl': '40px', + '3xl': '64px', + }, + brightness: { + 0: '0', + 50: '.5', + 75: '.75', + 90: '.9', + 95: '.95', + 100: '1', + 105: '1.05', + 110: '1.1', + 125: '1.25', + 150: '1.5', + 200: '2', + }, + borderColor: (theme) => ({ + ...theme('colors'), + DEFAULT: theme('colors.gray.200', 'currentColor'), + }), + borderOpacity: (theme) => theme('opacity'), + borderRadius: { + none: '0px', + sm: '0.125rem', + DEFAULT: '0.25rem', + md: '0.375rem', + lg: '0.5rem', + xl: '0.75rem', + '2xl': '1rem', + '3xl': '1.5rem', + full: '9999px', + }, + borderWidth: { + DEFAULT: '1px', + 0: '0px', + 2: '2px', + 4: '4px', + 8: '8px', + }, + boxShadow: { + sm: '0 1px 2px 0 rgba(0, 0, 0, 0.05)', + DEFAULT: '0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06)', + md: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)', + lg: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)', + xl: '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)', + '2xl': '0 25px 50px -12px rgba(0, 0, 0, 0.25)', + inner: 'inset 0 2px 4px 0 rgba(0, 0, 0, 0.06)', + none: 'none', + }, + caretColor: (theme) => theme('colors'), + contrast: { + 0: '0', + 50: '.5', + 75: '.75', + 100: '1', + 125: '1.25', + 150: '1.5', + 200: '2', + }, + container: {}, + content: { + none: 'none', + }, + cursor: { + auto: 'auto', + default: 'default', + pointer: 'pointer', + wait: 'wait', + text: 'text', + move: 'move', + help: 'help', + 'not-allowed': 'not-allowed', + }, + divideColor: (theme) => theme('borderColor'), + divideOpacity: (theme) => theme('borderOpacity'), + divideWidth: (theme) => theme('borderWidth'), + dropShadow: { + sm: '0 1px 1px rgba(0,0,0,0.05)', + DEFAULT: ['0 1px 2px rgba(0, 0, 0, 0.1)', '0 1px 1px rgba(0, 0, 0, 0.06)'], + md: ['0 4px 3px rgba(0, 0, 0, 0.07)', '0 2px 2px rgba(0, 0, 0, 0.06)'], + lg: ['0 10px 8px rgba(0, 0, 0, 0.04)', '0 4px 3px rgba(0, 0, 0, 0.1)'], + xl: ['0 20px 13px rgba(0, 0, 0, 0.03)', '0 8px 5px rgba(0, 0, 0, 0.08)'], + '2xl': '0 25px 25px rgba(0, 0, 0, 0.15)', + none: '0 0 #0000', + }, + fill: { current: 'currentColor' }, + grayscale: { + 0: '0', + DEFAULT: '100%', + }, + hueRotate: { + '-180': '-180deg', + '-90': '-90deg', + '-60': '-60deg', + '-30': '-30deg', + '-15': '-15deg', + 0: '0deg', + 15: '15deg', + 30: '30deg', + 60: '60deg', + 90: '90deg', + 180: '180deg', + }, + invert: { + 0: '0', + DEFAULT: '100%', + }, + flex: { + 1: '1 1 0%', + auto: '1 1 auto', + initial: '0 1 auto', + none: 'none', + }, + flexGrow: { + 0: '0', + DEFAULT: '1', + }, + flexShrink: { + 0: '0', + DEFAULT: '1', + }, + fontFamily: { + sans: [ + 'ui-sans-serif', + 'system-ui', + '-apple-system', + 'BlinkMacSystemFont', + '"Segoe UI"', + 'Roboto', + '"Helvetica Neue"', + 'Arial', + '"Noto Sans"', + 'sans-serif', + '"Apple Color Emoji"', + '"Segoe UI Emoji"', + '"Segoe UI Symbol"', + '"Noto Color Emoji"', + ], + serif: ['ui-serif', 'Georgia', 'Cambria', '"Times New Roman"', 'Times', 'serif'], + mono: [ + 'ui-monospace', + 'SFMono-Regular', + 'Menlo', + 'Monaco', + 'Consolas', + '"Liberation Mono"', + '"Courier New"', + 'monospace', + ], + }, + fontSize: { + xs: ['0.75rem', { lineHeight: '1rem' }], + sm: ['0.875rem', { lineHeight: '1.25rem' }], + base: ['1rem', { lineHeight: '1.5rem' }], + lg: ['1.125rem', { lineHeight: '1.75rem' }], + xl: ['1.25rem', { lineHeight: '1.75rem' }], + '2xl': ['1.5rem', { lineHeight: '2rem' }], + '3xl': ['1.875rem', { lineHeight: '2.25rem' }], + '4xl': ['2.25rem', { lineHeight: '2.5rem' }], + '5xl': ['3rem', { lineHeight: '1' }], + '6xl': ['3.75rem', { lineHeight: '1' }], + '7xl': ['4.5rem', { lineHeight: '1' }], + '8xl': ['6rem', { lineHeight: '1' }], + '9xl': ['8rem', { lineHeight: '1' }], + }, + fontWeight: { + thin: '100', + extralight: '200', + light: '300', + normal: '400', + medium: '500', + semibold: '600', + bold: '700', + extrabold: '800', + black: '900', + }, + gap: (theme) => theme('spacing'), + gradientColorStops: (theme) => theme('colors'), + gridAutoColumns: { + auto: 'auto', + min: 'min-content', + max: 'max-content', + fr: 'minmax(0, 1fr)', + }, + gridAutoRows: { + auto: 'auto', + min: 'min-content', + max: 'max-content', + fr: 'minmax(0, 1fr)', + }, + gridColumn: { + auto: 'auto', + 'span-1': 'span 1 / span 1', + 'span-2': 'span 2 / span 2', + 'span-3': 'span 3 / span 3', + 'span-4': 'span 4 / span 4', + 'span-5': 'span 5 / span 5', + 'span-6': 'span 6 / span 6', + 'span-7': 'span 7 / span 7', + 'span-8': 'span 8 / span 8', + 'span-9': 'span 9 / span 9', + 'span-10': 'span 10 / span 10', + 'span-11': 'span 11 / span 11', + 'span-12': 'span 12 / span 12', + 'span-full': '1 / -1', + }, + gridColumnEnd: { + auto: 'auto', + 1: '1', + 2: '2', + 3: '3', + 4: '4', + 5: '5', + 6: '6', + 7: '7', + 8: '8', + 9: '9', + 10: '10', + 11: '11', + 12: '12', + 13: '13', + }, + gridColumnStart: { + auto: 'auto', + 1: '1', + 2: '2', + 3: '3', + 4: '4', + 5: '5', + 6: '6', + 7: '7', + 8: '8', + 9: '9', + 10: '10', + 11: '11', + 12: '12', + 13: '13', + }, + gridRow: { + auto: 'auto', + 'span-1': 'span 1 / span 1', + 'span-2': 'span 2 / span 2', + 'span-3': 'span 3 / span 3', + 'span-4': 'span 4 / span 4', + 'span-5': 'span 5 / span 5', + 'span-6': 'span 6 / span 6', + 'span-full': '1 / -1', + }, + gridRowStart: { + auto: 'auto', + 1: '1', + 2: '2', + 3: '3', + 4: '4', + 5: '5', + 6: '6', + 7: '7', + }, + gridRowEnd: { + auto: 'auto', + 1: '1', + 2: '2', + 3: '3', + 4: '4', + 5: '5', + 6: '6', + 7: '7', + }, + gridTemplateColumns: { + none: 'none', + 1: 'repeat(1, minmax(0, 1fr))', + 2: 'repeat(2, minmax(0, 1fr))', + 3: 'repeat(3, minmax(0, 1fr))', + 4: 'repeat(4, minmax(0, 1fr))', + 5: 'repeat(5, minmax(0, 1fr))', + 6: 'repeat(6, minmax(0, 1fr))', + 7: 'repeat(7, minmax(0, 1fr))', + 8: 'repeat(8, minmax(0, 1fr))', + 9: 'repeat(9, minmax(0, 1fr))', + 10: 'repeat(10, minmax(0, 1fr))', + 11: 'repeat(11, minmax(0, 1fr))', + 12: 'repeat(12, minmax(0, 1fr))', + }, + gridTemplateRows: { + none: 'none', + 1: 'repeat(1, minmax(0, 1fr))', + 2: 'repeat(2, minmax(0, 1fr))', + 3: 'repeat(3, minmax(0, 1fr))', + 4: 'repeat(4, minmax(0, 1fr))', + 5: 'repeat(5, minmax(0, 1fr))', + 6: 'repeat(6, minmax(0, 1fr))', + }, + height: (theme) => ({ + auto: 'auto', + ...theme('spacing'), + '1/2': '50%', + '1/3': '33.333333%', + '2/3': '66.666667%', + '1/4': '25%', + '2/4': '50%', + '3/4': '75%', + '1/5': '20%', + '2/5': '40%', + '3/5': '60%', + '4/5': '80%', + '1/6': '16.666667%', + '2/6': '33.333333%', + '3/6': '50%', + '4/6': '66.666667%', + '5/6': '83.333333%', + full: '100%', + screen: '100vh', + }), + inset: (theme, { negative }) => ({ + auto: 'auto', + ...theme('spacing'), + ...negative(theme('spacing')), + '1/2': '50%', + '1/3': '33.333333%', + '2/3': '66.666667%', + '1/4': '25%', + '2/4': '50%', + '3/4': '75%', + full: '100%', + '-1/2': '-50%', + '-1/3': '-33.333333%', + '-2/3': '-66.666667%', + '-1/4': '-25%', + '-2/4': '-50%', + '-3/4': '-75%', + '-full': '-100%', + }), + keyframes: { + spin: { + to: { + transform: 'rotate(360deg)', + }, + }, + ping: { + '75%, 100%': { + transform: 'scale(2)', + opacity: '0', + }, + }, + pulse: { + '50%': { + opacity: '.5', + }, + }, + bounce: { + '0%, 100%': { + transform: 'translateY(-25%)', + animationTimingFunction: 'cubic-bezier(0.8,0,1,1)', + }, + '50%': { + transform: 'none', + animationTimingFunction: 'cubic-bezier(0,0,0.2,1)', + }, + }, + }, + letterSpacing: { + tighter: '-0.05em', + tight: '-0.025em', + normal: '0em', + wide: '0.025em', + wider: '0.05em', + widest: '0.1em', + }, + lineHeight: { + none: '1', + tight: '1.25', + snug: '1.375', + normal: '1.5', + relaxed: '1.625', + loose: '2', + 3: '.75rem', + 4: '1rem', + 5: '1.25rem', + 6: '1.5rem', + 7: '1.75rem', + 8: '2rem', + 9: '2.25rem', + 10: '2.5rem', + }, + listStyleType: { + none: 'none', + disc: 'disc', + decimal: 'decimal', + }, + margin: (theme, { negative }) => ({ + auto: 'auto', + ...theme('spacing'), + ...negative(theme('spacing')), + }), + maxHeight: (theme) => ({ + ...theme('spacing'), + full: '100%', + screen: '100vh', + }), + maxWidth: (theme, { breakpoints }) => ({ + none: 'none', + 0: '0rem', + xs: '20rem', + sm: '24rem', + md: '28rem', + lg: '32rem', + xl: '36rem', + '2xl': '42rem', + '3xl': '48rem', + '4xl': '56rem', + '5xl': '64rem', + '6xl': '72rem', + '7xl': '80rem', + full: '100%', + min: 'min-content', + max: 'max-content', + prose: '65ch', + ...breakpoints(theme('screens')), + }), + minHeight: { + 0: '0px', + full: '100%', + screen: '100vh', + }, + minWidth: { + 0: '0px', + full: '100%', + min: 'min-content', + max: 'max-content', + }, + objectPosition: { + bottom: 'bottom', + center: 'center', + left: 'left', + 'left-bottom': 'left bottom', + 'left-top': 'left top', + right: 'right', + 'right-bottom': 'right bottom', + 'right-top': 'right top', + top: 'top', + }, + opacity: { + 0: '0', + 5: '0.05', + 10: '0.1', + 20: '0.2', + 25: '0.25', + 30: '0.3', + 40: '0.4', + 50: '0.5', + 60: '0.6', + 70: '0.7', + 75: '0.75', + 80: '0.8', + 90: '0.9', + 95: '0.95', + 100: '1', + }, + order: { + first: '-9999', + last: '9999', + none: '0', + 1: '1', + 2: '2', + 3: '3', + 4: '4', + 5: '5', + 6: '6', + 7: '7', + 8: '8', + 9: '9', + 10: '10', + 11: '11', + 12: '12', + }, + outline: { + none: ['2px solid transparent', '2px'], + white: ['2px dotted white', '2px'], + black: ['2px dotted black', '2px'], + }, + padding: (theme) => theme('spacing'), + placeholderColor: (theme) => theme('colors'), + placeholderOpacity: (theme) => theme('opacity'), + ringColor: (theme) => ({ + DEFAULT: theme('colors.blue.500', '#3b82f6'), + ...theme('colors'), + }), + ringOffsetColor: (theme) => theme('colors'), + ringOffsetWidth: { + 0: '0px', + 1: '1px', + 2: '2px', + 4: '4px', + 8: '8px', + }, + ringOpacity: (theme) => ({ + DEFAULT: '0.5', + ...theme('opacity'), + }), + ringWidth: { + DEFAULT: '3px', + 0: '0px', + 1: '1px', + 2: '2px', + 4: '4px', + 8: '8px', + }, + rotate: { + '-180': '-180deg', + '-90': '-90deg', + '-45': '-45deg', + '-12': '-12deg', + '-6': '-6deg', + '-3': '-3deg', + '-2': '-2deg', + '-1': '-1deg', + 0: '0deg', + 1: '1deg', + 2: '2deg', + 3: '3deg', + 6: '6deg', + 12: '12deg', + 45: '45deg', + 90: '90deg', + 180: '180deg', + }, + saturate: { + 0: '0', + 50: '.5', + 100: '1', + 150: '1.5', + 200: '2', + }, + scale: { + 0: '0', + 50: '.5', + 75: '.75', + 90: '.9', + 95: '.95', + 100: '1', + 105: '1.05', + 110: '1.1', + 125: '1.25', + 150: '1.5', + }, + sepia: { + 0: '0', + DEFAULT: '100%', + }, + skew: { + '-12': '-12deg', + '-6': '-6deg', + '-3': '-3deg', + '-2': '-2deg', + '-1': '-1deg', + 0: '0deg', + 1: '1deg', + 2: '2deg', + 3: '3deg', + 6: '6deg', + 12: '12deg', + }, + space: (theme, { negative }) => ({ + ...theme('spacing'), + ...negative(theme('spacing')), + }), + stroke: { + current: 'currentColor', + }, + strokeWidth: { + 0: '0', + 1: '1', + 2: '2', + }, + textColor: (theme) => theme('colors'), + textOpacity: (theme) => theme('opacity'), + transformOrigin: { + center: 'center', + top: 'top', + 'top-right': 'top right', + right: 'right', + 'bottom-right': 'bottom right', + bottom: 'bottom', + 'bottom-left': 'bottom left', + left: 'left', + 'top-left': 'top left', + }, + transitionDelay: { + 75: '75ms', + 100: '100ms', + 150: '150ms', + 200: '200ms', + 300: '300ms', + 500: '500ms', + 700: '700ms', + 1000: '1000ms', + }, + transitionDuration: { + DEFAULT: '150ms', + 75: '75ms', + 100: '100ms', + 150: '150ms', + 200: '200ms', + 300: '300ms', + 500: '500ms', + 700: '700ms', + 1000: '1000ms', + }, + transitionProperty: { + none: 'none', + all: 'all', + DEFAULT: + 'background-color, border-color, color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter', + colors: 'background-color, border-color, color, fill, stroke', + opacity: 'opacity', + shadow: 'box-shadow', + transform: 'transform', + }, + transitionTimingFunction: { + DEFAULT: 'cubic-bezier(0.4, 0, 0.2, 1)', + linear: 'linear', + in: 'cubic-bezier(0.4, 0, 1, 1)', + out: 'cubic-bezier(0, 0, 0.2, 1)', + 'in-out': 'cubic-bezier(0.4, 0, 0.2, 1)', + }, + translate: (theme, { negative }) => ({ + ...theme('spacing'), + ...negative(theme('spacing')), + '1/2': '50%', + '1/3': '33.333333%', + '2/3': '66.666667%', + '1/4': '25%', + '2/4': '50%', + '3/4': '75%', + full: '100%', + '-1/2': '-50%', + '-1/3': '-33.333333%', + '-2/3': '-66.666667%', + '-1/4': '-25%', + '-2/4': '-50%', + '-3/4': '-75%', + '-full': '-100%', + }), + width: (theme) => ({ + auto: 'auto', + ...theme('spacing'), + '1/2': '50%', + '1/3': '33.333333%', + '2/3': '66.666667%', + '1/4': '25%', + '2/4': '50%', + '3/4': '75%', + '1/5': '20%', + '2/5': '40%', + '3/5': '60%', + '4/5': '80%', + '1/6': '16.666667%', + '2/6': '33.333333%', + '3/6': '50%', + '4/6': '66.666667%', + '5/6': '83.333333%', + '1/12': '8.333333%', + '2/12': '16.666667%', + '3/12': '25%', + '4/12': '33.333333%', + '5/12': '41.666667%', + '6/12': '50%', + '7/12': '58.333333%', + '8/12': '66.666667%', + '9/12': '75%', + '10/12': '83.333333%', + '11/12': '91.666667%', + full: '100%', + screen: '100vw', + min: 'min-content', + max: 'max-content', + }), + zIndex: { + auto: 'auto', + 0: '0', + 10: '10', + 20: '20', + 30: '30', + 40: '40', + 50: '50', + }, + }, + variantOrder: [ + 'first', + 'last', + 'odd', + 'even', + 'visited', + 'checked', + 'empty', + 'read-only', + 'group-hover', + 'group-focus', + 'focus-within', + 'hover', + 'focus', + 'focus-visible', + 'active', + 'disabled', + ], + variants: { + accessibility: ['responsive', 'focus-within', 'focus'], + alignContent: ['responsive'], + alignItems: ['responsive'], + alignSelf: ['responsive'], + animation: ['responsive'], + appearance: ['responsive'], + backdropBlur: ['responsive'], + backdropBrightness: ['responsive'], + backdropContrast: ['responsive'], + backdropDropShadow: ['responsive'], + backdropFilter: ['responsive'], + backdropGrayscale: ['responsive'], + backdropHueRotate: ['responsive'], + backdropInvert: ['responsive'], + backdropSaturate: ['responsive'], + backdropSepia: ['responsive'], + backgroundAttachment: ['responsive'], + backgroundBlendMode: ['responsive'], + backgroundClip: ['responsive'], + backgroundColor: ['responsive', 'dark', 'group-hover', 'focus-within', 'hover', 'focus'], + backgroundImage: ['responsive'], + backgroundOpacity: ['responsive', 'dark', 'group-hover', 'focus-within', 'hover', 'focus'], + backgroundPosition: ['responsive'], + backgroundRepeat: ['responsive'], + backgroundSize: ['responsive'], + backgroundOrigin: ['responsive'], + blur: ['responsive'], + borderCollapse: ['responsive'], + borderColor: ['responsive', 'dark', 'group-hover', 'focus-within', 'hover', 'focus'], + borderOpacity: ['responsive', 'dark', 'group-hover', 'focus-within', 'hover', 'focus'], + borderRadius: ['responsive'], + borderStyle: ['responsive'], + borderWidth: ['responsive'], + boxDecorationBreak: ['responsive'], + boxShadow: ['responsive', 'group-hover', 'focus-within', 'hover', 'focus'], + boxSizing: ['responsive'], + brightness: ['responsive'], + clear: ['responsive'], + container: ['responsive'], + contrast: ['responsive'], + cursor: ['responsive'], + display: ['responsive'], + divideColor: ['responsive', 'dark'], + divideOpacity: ['responsive', 'dark'], + divideStyle: ['responsive'], + divideWidth: ['responsive'], + dropShadow: ['responsive'], + fill: ['responsive'], + filter: ['responsive'], + flex: ['responsive'], + flexDirection: ['responsive'], + flexGrow: ['responsive'], + flexShrink: ['responsive'], + flexWrap: ['responsive'], + float: ['responsive'], + fontFamily: ['responsive'], + fontSize: ['responsive'], + fontSmoothing: ['responsive'], + fontStyle: ['responsive'], + fontVariantNumeric: ['responsive'], + fontWeight: ['responsive'], + gap: ['responsive'], + gradientColorStops: ['responsive', 'dark', 'hover', 'focus'], + grayscale: ['responsive'], + gridAutoColumns: ['responsive'], + gridAutoFlow: ['responsive'], + gridAutoRows: ['responsive'], + gridColumn: ['responsive'], + gridColumnEnd: ['responsive'], + gridColumnStart: ['responsive'], + gridRow: ['responsive'], + gridRowEnd: ['responsive'], + gridRowStart: ['responsive'], + gridTemplateColumns: ['responsive'], + gridTemplateRows: ['responsive'], + height: ['responsive'], + hueRotate: ['responsive'], + inset: ['responsive'], + invert: ['responsive'], + isolation: ['responsive'], + justifyContent: ['responsive'], + justifyItems: ['responsive'], + justifySelf: ['responsive'], + letterSpacing: ['responsive'], + lineHeight: ['responsive'], + listStylePosition: ['responsive'], + listStyleType: ['responsive'], + margin: ['responsive'], + maxHeight: ['responsive'], + maxWidth: ['responsive'], + minHeight: ['responsive'], + minWidth: ['responsive'], + mixBlendMode: ['responsive'], + objectFit: ['responsive'], + objectPosition: ['responsive'], + opacity: ['responsive', 'group-hover', 'focus-within', 'hover', 'focus'], + order: ['responsive'], + outline: ['responsive', 'focus-within', 'focus'], + overflow: ['responsive'], + overscrollBehavior: ['responsive'], + padding: ['responsive'], + placeContent: ['responsive'], + placeItems: ['responsive'], + placeSelf: ['responsive'], + placeholderColor: ['responsive', 'dark', 'focus'], + placeholderOpacity: ['responsive', 'dark', 'focus'], + pointerEvents: ['responsive'], + position: ['responsive'], + resize: ['responsive'], + ringColor: ['responsive', 'dark', 'focus-within', 'focus'], + ringOffsetColor: ['responsive', 'dark', 'focus-within', 'focus'], + ringOffsetWidth: ['responsive', 'focus-within', 'focus'], + ringOpacity: ['responsive', 'dark', 'focus-within', 'focus'], + ringWidth: ['responsive', 'focus-within', 'focus'], + rotate: ['responsive', 'hover', 'focus'], + saturate: ['responsive'], + scale: ['responsive', 'hover', 'focus'], + sepia: ['responsive'], + skew: ['responsive', 'hover', 'focus'], + space: ['responsive'], + stroke: ['responsive'], + strokeWidth: ['responsive'], + tableLayout: ['responsive'], + textAlign: ['responsive'], + textColor: ['responsive', 'dark', 'group-hover', 'focus-within', 'hover', 'focus'], + textDecoration: ['responsive', 'group-hover', 'focus-within', 'hover', 'focus'], + textOpacity: ['responsive', 'dark', 'group-hover', 'focus-within', 'hover', 'focus'], + textOverflow: ['responsive'], + textTransform: ['responsive'], + transform: ['responsive'], + transformOrigin: ['responsive'], + transitionDelay: ['responsive'], + transitionDuration: ['responsive'], + transitionProperty: ['responsive'], + transitionTimingFunction: ['responsive'], + translate: ['responsive', 'hover', 'focus'], + userSelect: ['responsive'], + verticalAlign: ['responsive'], + visibility: ['responsive'], + whitespace: ['responsive'], + width: ['responsive'], + wordBreak: ['responsive'], + zIndex: ['responsive', 'focus-within', 'focus'], + }, + plugins: [], +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..a7ca1f2 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,42 @@ +{ + "compilerOptions": { + "target": "esnext", + "module": "esnext", + "strict": false, + "jsx": "preserve", + "importHelpers": true, + "moduleResolution": "node", + "experimentalDecorators": true, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strictPropertyInitialization": false, + "sourceMap": true, + "noImplicitAny": false, + "baseUrl": ".", + "types": [ + "webpack-env" + ], + "paths": { + "@/*": [ + "src/*" + ] + }, + "lib": [ + "esnext", + "dom", + "dom.iterable", + "scripthost" + ] + }, + "include": [ + "src/**/*.ts", + "src/**/*.tsx", + "src/**/*.vue", + "tests/**/*.ts", + "tests/**/*.tsx" + ], + "exclude": [ + "node_modules" + ] +} diff --git a/vue.config.js b/vue.config.js new file mode 100644 index 0000000..cfea7dd --- /dev/null +++ b/vue.config.js @@ -0,0 +1,106 @@ +const TerserPlugin = require('terser-webpack-plugin'); +const copyWebpackPlugin = require('copy-webpack-plugin') +const purgecss = require('@fullhuman/postcss-purgecss')({ + content: [ + './src/**/*.html', + './src/**/*.vue', + './src/**/*.jsx', + ], + + // Include any special characters you're using in this regular expression + defaultExtractor: content => content.match(/[\w-/:]+(? { + // const svgRule = config.module.rule('svg') + // svgRule.uses.clear() + // svgRule + // .use('svg-sprite-loader') + // .loader('svg-sprite-loader') + // .options({ + // symbolId: 'icon-[name]' + // }); + config.optimization.minimizer('terser') + .tap(items => { + items = items.map(item => { + Object.assign(item.terserOptions.compress, { + pure_funcs: ['console.log'], + drop_console: true + }) + return item; + }) + return items + }) + + config.module + .rule('svg') + .exclude.add(resolve('src/icons')) + .end() + config.module + .rule('icons') + .test(/\.svg$/) + .include.add(resolve('src/icons')) + .end() + .use('svg-sprite-loader') + .loader('svg-sprite-loader') + .options({ + symbolId: 'icon-[name]' + }) + .end() + + return config; + }, + configureWebpack: { + plugins: [ + new copyWebpackPlugin([ + {from: "public/.htaccess", to: "dashboard/"}, + {from: "public/.htaccess", to: "admin/"}, + {from: "public/.htaccess", to: "wap/"}, + ], {copyUnmodified: true}), + + ], + resolve: { + alias: { + '@': resolve('src'), + 'views': resolve('src/views') + } + } + } +} \ No newline at end of file