完成几个页面

This commit is contained in:
2019-05-28 20:44:55 +08:00
parent 54ecf61073
commit b55ffb3f17
48 changed files with 18727 additions and 40 deletions
@@ -0,0 +1,40 @@
package net.alawn.bill.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* @Author: Alawn
* @Description:
* @Date: Create in 16:53 2019-05-17
*/
//@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
// public void addResourceHandlers(ResourceHandlerRegistry registry) {
// registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");
// }
@Override
public void addCorsMappings(CorsRegistry registry) {
// registry.addMapping("/basic/**")
// .allowedOrigins("http://domain2.com")
// .allowedMethods("GET","POST","PUT", "DELETE")
// .allowedHeaders("header1", "header2", "header3")
// .exposedHeaders("header1", "header2")
// .allowCredentials(false).maxAge(3600);
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("PUT", "DELETE", "GET", "POST", "OPTIONS")
.allowedHeaders("*")
.exposedHeaders("access-control-allow-headers",
"access-control-allow-methods",
"access-control-allow-origin",
"access-control-max-age",
"X-Frame-Options")
.allowCredentials(true).maxAge(3600);
}
}
@@ -1,5 +1,7 @@
package net.alawn.bill.security;
package net.alawn.bill.config;
import net.alawn.bill.security.JwtAuthenticationEntryPoint;
import net.alawn.bill.security.JwtAuthenticationTokenFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
@@ -21,6 +23,11 @@ import org.springframework.security.web.authentication.UsernamePasswordAuthentic
import org.springframework.security.web.firewall.HttpFirewall;
import org.springframework.security.web.firewall.StrictHttpFirewall;
import org.springframework.util.DigestUtils;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import java.util.Arrays;
/**
* @Author: Alawn
@@ -78,7 +85,10 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
/** Security Http 配置 */
@Override
protected void configure(HttpSecurity http) throws Exception {
// http // 去掉跨域
http.csrf().disable() // 去掉跨域
.cors()
.and()
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler)
.and()
.authorizeRequests()
@@ -86,6 +96,7 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
.antMatchers("/druid/**").permitAll()
.antMatchers("/swagger*").permitAll()
.antMatchers( HttpMethod.GET, "/","/*.html", "/favicon.ico", "/**/*.html", "/**/*.css", "/**/*.js").permitAll()
// .antMatchers("/resources/**").permitAll()
.antMatchers("/auth/**").permitAll()
.antMatchers("/bill/**").authenticated()
.antMatchers("/basic/**/*").authenticated()
@@ -104,6 +115,26 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
http.headers().cacheControl(); // 禁用缓存
}
/** Security 跨域设置 */
@Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("*"));
configuration.setAllowedMethods(Arrays.asList("PUT", "DELETE", "GET", "POST", "OPTIONS"));
configuration.setAllowedHeaders(Arrays.asList("*"));
configuration.addExposedHeader("Location"); // 暴露 Location header
configuration.addExposedHeader("access-control-allow-headers");
configuration.addExposedHeader("access-control-allow-methods");
configuration.addExposedHeader("access-control-allow-origin");
configuration.addExposedHeader("access-control-max-age");
configuration.addExposedHeader("X-Frame-Options");
configuration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
/** Security Web 配置 */
@Override
public void configure(WebSecurity web) throws Exception {
@@ -15,6 +15,7 @@ import org.springframework.web.bind.annotation.*;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.Principal;
import java.util.List;
import java.util.Optional;
@@ -47,7 +48,7 @@ public class BasicController {
@Autowired
TitleRepo titleRepo;
@RequestMapping(value = "/post",method = RequestMethod.POST)
@RequestMapping(value = "/posts",method = RequestMethod.POST)
@ResponseBody
public ResponseEntity<Post> addPost(@RequestBody AddPost addPost, Principal principal) throws URISyntaxException {
AuthUser authUser = (AuthUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
@@ -61,7 +62,17 @@ public class BasicController {
return ResponseEntity.ok(post);
}
@RequestMapping(value = "/post/{id}",method = RequestMethod.GET)
@RequestMapping(value = "/posts",method = RequestMethod.GET)
@ResponseBody
public ResponseEntity<List<Post>> fetchPosts() {
AuthUser authUser = (AuthUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
long userId = authUser.getId();
List<Post> posts = postRepo.findByUserId(userId);
return ResponseEntity.ok(posts);
}
@RequestMapping(value = "/posts/{id}",method = RequestMethod.GET)
@ResponseBody
public ResponseEntity<Post> getPost(@PathVariable long id){
log.info("post id:" + id);
@@ -78,7 +89,7 @@ public class BasicController {
}
@RequestMapping(value = "/merchant",method = RequestMethod.POST)
@RequestMapping(value = "/merchants",method = RequestMethod.POST)
@ResponseBody
public ResponseEntity<Merchant> addMerchant(@RequestBody AddMerchant addMerchant) throws URISyntaxException {
/** 登记 Post */
@@ -87,7 +98,15 @@ public class BasicController {
return ResponseEntity.ok(merchant);
}
@RequestMapping(value = "/merchant/{id}",method = RequestMethod.GET)
@RequestMapping(value = "/merchants",method = RequestMethod.GET)
@ResponseBody
public ResponseEntity<List<Merchant>> fetchMerchants() throws URISyntaxException {
List<Merchant> merchants = merchantRepo.findAll();
return ResponseEntity.ok(merchants);
}
@RequestMapping(value = "/merchants/{id}",method = RequestMethod.GET)
@ResponseBody
public ResponseEntity<Merchant> getMerchant(@PathVariable long id){
/** 登记 Post */
@@ -100,21 +119,26 @@ public class BasicController {
}
}
@RequestMapping(value = "/title",method = RequestMethod.POST)
@RequestMapping(value = "/titles",method = RequestMethod.POST)
@ResponseBody
public ResponseEntity<Title> addTitle(@RequestBody AddTitle addTitle) throws URISyntaxException {
/** 登记 Post */
public ResponseEntity<Title> addTitle(@RequestBody AddTitle addTitle) {
/** 登记 Title */
Title title = titleRepo.save(addTitle.build());
titleRepo.flush();
return ResponseEntity.ok(title);
}
@RequestMapping(value = "/title/{id}",method = RequestMethod.GET)
@RequestMapping(value = "/titles",method = RequestMethod.GET)
@ResponseBody
public ResponseEntity<List<Title>> addTitle() {
List<Title> titles = titleRepo.findAll();
return ResponseEntity.ok(titles);
}
@RequestMapping(value = "/titles/{id}",method = RequestMethod.GET)
@ResponseBody
public ResponseEntity<Title> getTitle(@PathVariable long id){
/** 登记 Post */
/** 登记 Title */
Optional<Title> title = titleRepo.findById(id);
if(title.isPresent()) {
return ResponseEntity.ok(title.get());
@@ -1,5 +1,6 @@
package net.alawn.bill.entity.po;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.*;
import javax.persistence.Column;
@@ -20,6 +21,7 @@ import javax.persistence.Table;
@NoArgsConstructor
@AllArgsConstructor
public class Post extends Base{
@JsonIgnore
private long userId ;
private String address ;
private String contact ;
@@ -1,24 +0,0 @@
package net.alawn.bill.exception;
/**
* @Author: Alawn
* @Description:
* @Date: Create in 15:52 2019-03-28
*/
public class UserAlreadyExistException extends CommonException {
private final static RespMsg msg = RespMsg.USER_ALREADY_EXIST;
public UserAlreadyExistException() {
super(msg.code(),msg.message());
}
public UserAlreadyExistException(String message) {
super(msg.code(),message);
}
public UserAlreadyExistException(String format, Object... arguments) {
super(msg.code(),format, arguments);
}
}
@@ -1,6 +1,5 @@
package net.alawn.bill.repository;
import net.alawn.bill.entity.po.Book;
import net.alawn.bill.entity.po.Item;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@@ -5,6 +5,8 @@ import net.alawn.bill.entity.po.Merchant;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* @Author: Alawn
* @Description:
@@ -13,5 +15,4 @@ import org.springframework.stereotype.Repository;
@Repository
public interface MerchantRepo extends JpaRepository<Merchant, Long> {
}
@@ -5,6 +5,8 @@ import net.alawn.bill.entity.po.Post;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* @Author: Alawn
* @Description:
@@ -13,5 +15,5 @@ import org.springframework.stereotype.Repository;
@Repository
public interface PostRepo extends JpaRepository<Post, Long> {
List<Post> findByUserId(long userId);
}
+2 -1
View File
@@ -3,7 +3,8 @@ server.port= 30000
# JWT
jwt.header=Authorization
jwt.secret=8UbBmq9LNnPKFc4sFN7ZF2TTCGbCXTsy
jwt.expiration=604800
jwt.expiration=999604800
#jwt.expiration=604800
jwt.tokenHead=Bearer
jwt.route.authentication.path=auth
jwt.route.authentication.refresh=refresh
+18
View File
@@ -0,0 +1,18 @@
{
"presets": [
["env", {
"modules": false,
"targets": {
"browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
}
}],
"stage-2"
],
"plugins": ["transform-vue-jsx", "transform-runtime"],
"env": {
"test": {
"presets": ["env", "stage-2"],
"plugins": ["transform-vue-jsx", "transform-es2015-modules-commonjs", "dynamic-import-node"]
}
}
}
+9
View File
@@ -0,0 +1,9 @@
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
+5
View File
@@ -0,0 +1,5 @@
/build/
/config/
/dist/
/*.js
/test/unit/coverage/
+29
View File
@@ -0,0 +1,29 @@
// https://eslint.org/docs/user-guide/configuring
module.exports = {
root: true,
parserOptions: {
parser: 'babel-eslint'
},
env: {
browser: true,
},
extends: [
// https://github.com/vuejs/eslint-plugin-vue#priority-a-essential-error-prevention
// consider switching to `plugin:vue/strongly-recommended` or `plugin:vue/recommended` for stricter rules.
'plugin:vue/essential',
// https://github.com/standard/standard/blob/master/docs/RULES-en.md
'standard'
],
// required to lint *.vue files
plugins: [
'vue'
],
// add your custom rules here
rules: {
// allow async-await
'generator-star-spacing': 'off',
// allow debugger during development
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
}
}
+17
View File
@@ -0,0 +1,17 @@
.DS_Store
node_modules/
/dist/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
/test/unit/coverage/
/test/e2e/reports/
selenium-debug.log
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
+10
View File
@@ -0,0 +1,10 @@
// https://github.com/michael-ciniawsky/postcss-load-config
module.exports = {
"plugins": {
"postcss-import": {},
"postcss-url": {},
// to edit target browsers: use "browserslist" field in package.json
"autoprefixer": {}
}
}
+30
View File
@@ -0,0 +1,30 @@
# bill
> bill
## Build Setup
``` bash
# install dependencies
npm install
# serve with hot reload at localhost:8080
npm run dev
# build for production with minification
npm run build
# build for production and view the bundle analyzer report
npm run build --report
# run unit tests
npm run unit
# run e2e tests
npm run e2e
# run all tests
npm test
```
For a detailed explanation on how things work, check out the [guide](http://vuejs-templates.github.io/webpack/) and [docs for vue-loader](http://vuejs.github.io/vue-loader).
+7
View File
@@ -0,0 +1,7 @@
'use strict'
const merge = require('webpack-merge')
const prodEnv = require('./prod.env')
module.exports = merge(prodEnv, {
NODE_ENV: '"development"'
})
+76
View File
@@ -0,0 +1,76 @@
'use strict'
// Template version: 1.3.1
// see http://vuejs-templates.github.io/webpack for documentation.
const path = require('path')
module.exports = {
dev: {
// Paths
assetsSubDirectory: 'static',
assetsPublicPath: '/',
proxyTable: {},
// Various Dev Server settings
host: 'localhost', // can be overwritten by process.env.HOST
port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined
autoOpenBrowser: false,
errorOverlay: true,
notifyOnErrors: true,
poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions-
// Use Eslint Loader?
// If true, your code will be linted during bundling and
// linting errors and warnings will be shown in the console.
useEslint: true,
// If true, eslint errors and warnings will also be shown in the error overlay
// in the browser.
showEslintErrorsInOverlay: false,
/**
* Source Maps
*/
// https://webpack.js.org/configuration/devtool/#development
devtool: 'cheap-module-eval-source-map',
// If you have problems debugging vue-files in devtools,
// set this to false - it *may* help
// https://vue-loader.vuejs.org/en/options.html#cachebusting
cacheBusting: true,
cssSourceMap: true
},
build: {
// Template for index.html
index: path.resolve(__dirname, '../dist/index.html'),
// Paths
assetsRoot: path.resolve(__dirname, '../dist'),
assetsSubDirectory: 'static',
assetsPublicPath: '/',
/**
* Source Maps
*/
productionSourceMap: true,
// https://webpack.js.org/configuration/devtool/#production
devtool: '#source-map',
// Gzip off by default as many popular static hosts such as
// Surge or Netlify already gzip all static assets for you.
// Before setting to `true`, make sure to:
// npm install --save-dev compression-webpack-plugin
productionGzip: false,
productionGzipExtensions: ['js', 'css'],
// Run the build command with an extra argument to
// View the bundle analyzer report after build finishes:
// `npm run build --report`
// Set to `true` or `false` to always turn it on or off
bundleAnalyzerReport: process.env.npm_config_report
}
}
+4
View File
@@ -0,0 +1,4 @@
'use strict'
module.exports = {
NODE_ENV: '"production"'
}
+7
View File
@@ -0,0 +1,7 @@
'use strict'
const merge = require('webpack-merge')
const devEnv = require('./dev.env')
module.exports = merge(devEnv, {
NODE_ENV: '"testing"'
})
+12
View File
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>bill</title>
</head>
<body>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
+16235
View File
File diff suppressed because it is too large Load Diff
+89
View File
@@ -0,0 +1,89 @@
{
"name": "bill",
"version": "1.0.0",
"description": "bill",
"author": "alawn.cn@gmail.com",
"private": true,
"scripts": {
"dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
"start": "npm run dev",
"unit": "jest --config test/unit/jest.conf.js --coverage",
"e2e": "node test/e2e/runner.js",
"test": "npm run unit && npm run e2e",
"lint": "eslint --ext .js,.vue src test/unit test/e2e/specs",
"build": "node build/build.js"
},
"dependencies": {
"axios": "^0.18.0",
"element-ui": "^2.8.2",
"vue": "^2.5.2",
"vue-router": "^3.0.1"
},
"devDependencies": {
"autoprefixer": "^7.1.2",
"babel-core": "^6.22.1",
"babel-eslint": "^8.2.1",
"babel-helper-vue-jsx-merge-props": "^2.0.3",
"babel-jest": "^24.8.0",
"babel-loader": "^7.1.1",
"babel-plugin-dynamic-import-node": "^1.2.0",
"babel-plugin-syntax-jsx": "^6.18.0",
"babel-plugin-transform-es2015-modules-commonjs": "^6.26.0",
"babel-plugin-transform-runtime": "^6.22.0",
"babel-plugin-transform-vue-jsx": "^3.5.0",
"babel-preset-env": "^1.3.2",
"babel-preset-stage-2": "^6.22.0",
"babel-register": "^6.22.0",
"chalk": "^2.0.1",
"chromedriver": "^2.27.2",
"copy-webpack-plugin": "^4.0.1",
"cross-spawn": "^5.0.1",
"css-loader": "^2.1.1",
"eslint": "^4.15.0",
"eslint-config-standard": "^10.2.1",
"eslint-friendly-formatter": "^3.0.0",
"eslint-loader": "^1.7.1",
"eslint-plugin-import": "^2.7.0",
"eslint-plugin-node": "^5.2.0",
"eslint-plugin-promise": "^3.4.0",
"eslint-plugin-standard": "^3.0.1",
"eslint-plugin-vue": "^4.0.0",
"extract-text-webpack-plugin": "^3.0.0",
"file-loader": "^1.1.4",
"friendly-errors-webpack-plugin": "^1.6.1",
"html-webpack-plugin": "^2.30.1",
"jest": "^24.8.0",
"jest-serializer-vue": "^0.3.0",
"nightwatch": "^1.1.11",
"node-notifier": "^5.1.2",
"optimize-css-assets-webpack-plugin": "^5.0.1",
"ora": "^1.2.0",
"portfinder": "^1.0.13",
"postcss-import": "^11.0.0",
"postcss-loader": "^2.0.8",
"postcss-url": "^7.2.1",
"rimraf": "^2.6.0",
"selenium-server": "^3.0.1",
"semver": "^5.3.0",
"shelljs": "^0.7.6",
"uglifyjs-webpack-plugin": "^1.1.1",
"url-loader": "^1.1.2",
"vue-jest": "^1.0.2",
"vue-loader": "^13.3.0",
"vue-style-loader": "^3.0.1",
"vue-template-compiler": "^2.5.2",
"webpack": "^3.6.0",
"webpack-bundle-analyzer": "^3.3.2",
"webpack-dev-server": "^2.9.1",
"webpack-merge": "^4.1.0"
},
"engines": {
"node": ">= 6.0.0",
"npm": ">= 3.0.0"
},
"browserslist": [
"> 1%",
"last 2 versions",
"not ie <= 8"
]
}
+23
View File
@@ -0,0 +1,23 @@
<template>
<div id="app">
<!-- <img src="./assets/logo.png"> -->
<router-view/>
</div>
</template>
<script>
export default {
name: 'App'
}
</script>
<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
+31
View File
@@ -0,0 +1,31 @@
import HttpRequest from '@/axios/api.request'
HttpRequest.interceptors.request.use(
config => {
// if (localStorage.token) { //判断token是否存在
// config.headers.Authorization = localStorage.token; //将token设置成请求头
// }
config.headers.Authorization = "Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhbGF3biIsImNyZWF0ZWQiOjE1NTg2ODY4MTk0NjcsImlhdCI6MTU1ODY4NjgxOSwiZXhwIjoxNTU5MjkxNjE5fQ.N9ZSMrStIaUyngSpvmup_akXQaueLSIX5-h4Q_fixdM"; //将token设置成请求头
return config;
},
err => {
return Promise.reject(err);
}
);
export const MerchantAddAction = (merc) => {
return HttpRequest.request({
url:'basic/merchant',
method: 'post',
data: merc
})
}
export const MerchantGetAction = (id) => {
return HttpRequest.request({
url: 'basic/merchant/' + id,
method: 'get',
})
}
+60
View File
@@ -0,0 +1,60 @@
import HttpRequest from '@/axios/api.request'
export const addMerchant = (merc) => {
return HttpRequest.request({
url: 'basic/merchants',
method: 'post',
data: merc
})
}
export const fetchMerchants = () => {
return HttpRequest.request({
url: 'basic/merchants',
method: 'get'
})
}
export const fetchTitles = () => {
return HttpRequest.request({
url: 'basic/titles',
method: 'get'
})
}
export const getMerchant = (id) => {
return HttpRequest.request({
url: 'basic/merchants/' + id,
method: 'get'
})
}
export const addPost = (merc) => {
return HttpRequest.request({
url: 'basic/posts',
method: 'post',
data: merc
})
}
export const fetchPosts = () => {
return HttpRequest.request({
url: 'basic/posts',
method: 'get'
})
}
export const getPost = (id) => {
return HttpRequest.request({
url: 'basic/posts/' + id,
method: 'get'
})
}
export const newBill = (bill) => {
return HttpRequest.request({
url: 'bills/',
method: 'post',
data: bill
})
}
+9
View File
@@ -0,0 +1,9 @@
import HttpRequest from '@/axios/api.request'
export const UserLoginAction = (ruleForm) => {
return HttpRequest.request({
url: 'auth',
method: 'post',
data: ruleForm
})
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

@@ -0,0 +1,11 @@
import axios from 'axios'
import config from './index'
const baseUrl = process.env.NODE_ENV === 'development' ? config.baseUrl.dev : config.baseUrl.pro
const HttpRequest = axios.create({
// api的base_url
baseURL: baseUrl,
// 请求超时时间
timeout: 5000
})
export default HttpRequest
+10
View File
@@ -0,0 +1,10 @@
export default {
/**
* @description api请求基础路径
*/
baseUrl: {
dev: 'http://localhost:30000/',
pro: 'http://localhost:30000/'
// pro: 'http://101.132.124.171:8080/demo-1.0/api/'
}
}
@@ -0,0 +1,113 @@
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<h2>Essential Links</h2>
<ul>
<li>
<a
href="https://vuejs.org"
target="_blank"
>
Core Docs
</a>
</li>
<li>
<a
href="https://forum.vuejs.org"
target="_blank"
>
Forum
</a>
</li>
<li>
<a
href="https://chat.vuejs.org"
target="_blank"
>
Community Chat
</a>
</li>
<li>
<a
href="https://twitter.com/vuejs"
target="_blank"
>
Twitter
</a>
</li>
<br>
<li>
<a
href="http://vuejs-templates.github.io/webpack/"
target="_blank"
>
Docs for This Template
</a>
</li>
</ul>
<h2>Ecosystem</h2>
<ul>
<li>
<a
href="http://router.vuejs.org/"
target="_blank"
>
vue-router
</a>
</li>
<li>
<a
href="http://vuex.vuejs.org/"
target="_blank"
>
vuex
</a>
</li>
<li>
<a
href="http://vue-loader.vuejs.org/"
target="_blank"
>
vue-loader
</a>
</li>
<li>
<a
href="https://github.com/vuejs/awesome-vue"
target="_blank"
>
awesome-vue
</a>
</li>
</ul>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
data () {
return {
msg: 'Welcome to Your Vue.js App'
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h1, h2 {
font-weight: normal;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
</style>
+34
View File
@@ -0,0 +1,34 @@
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import router from './router'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import HttpRequest from '@/axios/api.request'
Vue.use(ElementUI)
Vue.config.productionTip = false
HttpRequest.interceptors.request.use(
config => {
// 判断token是否存在
if (localStorage.token) {
// 将token设置成请求头
config.headers.Authorization = localStorage.token
}
// config.headers.Authorization = "Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhbGF3biIsImNyZWF0ZWQiOjE1NTg2ODY4MTk0NjcsImlhdCI6MTU1ODY4NjgxOSwiZXhwIjoxNTU5MjkxNjE5fQ.N9ZSMrStIaUyngSpvmup_akXQaueLSIX5-h4Q_fixdM" //将token设置成请求头
return config
}, function(err) {
return Promise.reject(err)
}
)
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
components: { App },
template: '<App/>'
})
+30
View File
@@ -0,0 +1,30 @@
import Vue from 'vue'
import Router from 'vue-router'
import HelloWorld from '@/components/HelloWorld'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/',
name: 'HelloWorld',
component: HelloWorld
},
{
path: '/merchant',
name: 'Merchant',
component: () => import(/* webpackChunkName: "merchant" */ '../views/Merchant.vue')
},
{
path: '/login',
name: 'Login',
component: () => import(/* webpackChunkName: "merchant" */ '../views/Login.vue')
},
{
path: '/bill',
name: 'Bill',
component: () => import(/* webpackChunkName: "merchant" */ '../views/Bill.vue')
}
]
})
+23
View File
@@ -0,0 +1,23 @@
<el-table
:data="tableData"
border
style="width: 100%">
<el-table-column
prop="date"
label="日期"
width="180">
</el-table-column>
<el-table-column
prop="name"
label="抬头"
width="180">
</el-table-column>
<el-table-column
prop="address"
label="餐饮商户">
</el-table-column>
<el-table-column
prop="address"
label="金额">
</el-table-column>
</el-table>
+531
View File
@@ -0,0 +1,531 @@
<template>
<div class="main">
<div class="group">
<!-- <el-form ref="form" :model="form" :rules="rules" label-width="80px"> -->
<!-- <el-form ref="form" :model="form" label-width="800px"> -->
<el-form ref="billForm" :model="bill" :rules="rules" label-width="80px">
<el-form-item label="邮寄地址" prop="postContact">
<!--邮寄地址-->
<el-autocomplete
class="posts"
popper-class="my-autocomplete"
v-model="bill.postContact"
:fetch-suggestions="querySearchAsyncPosts"
placeholder="请输选择邮寄地址"
@select="handleSelectPosts" :disabled="disabled">
<i class="el-icon-edit el-input__icon" slot="suffix" @click="handleIconClickPosts"></i>
<template slot-scope="{ item }">
<div class="name">{{ item.contact }}</div>
<span class="subtitle">{{ item.address }}</span>
<span class="subtitle">;</span>
<span class="subtitle">{{ item.mobile }}</span>
<span class="subtitle">;</span>
</template>
</el-autocomplete>
</el-form-item>
<div class="gTable">
<!--增删改查-->
<!--明细-->
<!-- <el-form-item label="明细" prop="username"> -->
<!--分割线-->
<!-- <span><el-divider></el-divider></span> -->
<el-table :data="bill.billItems" height="400px" style="width: 100%">
<el-table-column prop="dating" label="日期" width="100" ></el-table-column>
<el-table-column prop="titleAbbr" label="抬头" width="150">
<template slot-scope="scope">
<el-popover trigger="hover" placement="top">
<p>税号: {{ scope.row.taxNo }}</p>
<p>抬头: {{ scope.row.titleName }}</p>
<div slot="reference" class="name-wrapper">
<el-tag size="medium">{{ scope.row.titleAbbr }}</el-tag>
</div>
</el-popover>
</template>
</el-table-column>
<el-table-column prop="merchantAbbr" label="餐饮商户" width="150" ></el-table-column>
<el-table-column prop="amount" label="金额" width="80" ></el-table-column>
<el-table-column align="right" >
<template slot="header" slot-scope="scope">
<!-- <el-button class="itemBtn" type="primary" size="medium" @click="clearFocusRow();isAddShow = true;resetForm('addForm');" :disabled="disabled">添加</el-button> -->
<el-button class="itemBtn" type="primary" size="small" @click="clearFocusRow();isAddShow = true;resetForm('addForm');" :disabled="disabled">添加</el-button>
</template>
<template slot-scope="scope">
<el-button-group>
<!-- type="success" -->
<el-button
size="mini"
icon="el-icon-edit"
@click="handleEdit(scope.$index, scope.row)" :disabled="disabled">编辑</el-button>
<!-- type="danger" -->
<el-button
size="mini"
icon="el-icon-delete"
@click.native.prevent="handleDelete(scope.$index,bill.billItems)" :disabled="disabled">删除</el-button>
</el-button-group>
</template>
</el-table-column>
</el-table>
<!-- </el-form-item> -->
</div>
<el-form-item label-width="2px">
<el-button class="submit" type="primary" @click="onSubmit('billForm')" :disabled="disabled">提交</el-button>
</el-form-item>
</el-form>
</div>
<!--增加-->
<div width="600px">
<el-dialog title="增加" :visible.sync="isAddShow" width="700px" >
<el-form ref="addForm" :model="focusRow" :rules="rules" label-width="80px">
<el-row>
<el-col :span="24">
<!--抬头-->
<el-form-item label="抬头简称" prop="titleAbbr">
<el-autocomplete class="titles" popper-class="my-autocomplete" v-model="focusRow.titleAbbr" :fetch-suggestions="querySearchAsyncTitles" placeholder="请输选择抬头" @select="handleSelectTitles" >
<i class="el-icon-edit el-input__icon" slot="suffix" @click="handleIconClickTitles" ></i>
<template slot-scope="{ item }">
<div class="name">{{ item.abbr }}</div>
<span class="subtitle">{{ item.name }}</span>
<span class="subtitle">{{ item.taxNo }}</span>
</template>
</el-autocomplete>
</el-form-item>
</el-col>
</el-row>
<!--商户-->
<el-row>
<el-col :span="24">
<el-form-item label="开票商家" prop="merchantAbbr">
<el-autocomplete class="merchants" v-model="focusRow.merchantAbbr" :fetch-suggestions="querySearchAsyncMerchant" placeholder="请选择餐饮商户" @select="handleSelectMerchant">
<i class="el-icon-edit el-input__icon" slot="suffix" @click="handleIconClickTitles" ></i>
<template slot-scope="{ item }">
<div class="name">{{ item.abbr }}</div>
<span class="subtitle">{{ item.name }}</span>
</template>
</el-autocomplete>
</el-form-item>
</el-col>
</el-row>
<!--日期 & 金额-->
<el-row>
<el-col :span="12">
<!--开票日期-->
<el-form-item label="开票日期" prop="dating">
<el-date-picker type="date" placeholder="选择日期" v-model="focusRow.dating" style="width: 100%;" value-format="yyyy-MM-dd"> </el-date-picker>
</el-form-item>
</el-col>
<el-col :span="1">
<!--金额-->
<el-form-item label="开票金额" prop="amount">
<el-input-number v-model="focusRow.amount" :precision="2" :step="100" controls-position="right" :min="0" :max="5000"></el-input-number>
</el-form-item>
</el-col>
</el-row>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="addItem('addForm')" :disabled="disabled">确 定</el-button>
<el-button @click="resetForm('addForm')">重置</el-button>
</div>
</el-dialog>
</div>
<!--编辑-->
<div width="600px">
<el-dialog title="编辑" :visible.sync="isEditShow" width="700px" >
<el-form ref="editForm" :model="focusRow" :rules="rules" label-width="80px">
<el-row>
<el-col :span="24">
<!--抬头-->
<el-form-item label="抬头简称" prop="titleAbbr">
<el-autocomplete class="titles" popper-class="my-autocomplete" v-model="focusRow.titleAbbr" :fetch-suggestions="querySearchAsyncTitles" placeholder="请输选择抬头" @select="handleSelectTitles" >
<i class="el-icon-edit el-input__icon" slot="suffix" @click="handleIconClickTitles" ></i>
<template slot-scope="{ item }">
<div class="name">{{ item.abbr }}</div>
<span class="subtitle">{{ item.name }}</span>
<span class="subtitle">{{ item.taxNo }}</span>
</template>
</el-autocomplete>
</el-form-item>
</el-col>
</el-row>
<!--商户-->
<el-row>
<el-col :span="24">
<el-form-item label="开票商家" prop="merchantAbbr">
<el-autocomplete class="merchants" v-model="focusRow.merchantAbbr" :fetch-suggestions="querySearchAsyncMerchant" placeholder="请选择餐饮商户" @select="handleSelectMerchant">
<i class="el-icon-edit el-input__icon" slot="suffix" @click="handleIconClickTitles" ></i>
<template slot-scope="{ item }">
<div class="name">{{ item.abbr }}</div>
<span class="subtitle">{{ item.name }}</span>
</template>
</el-autocomplete>
</el-form-item>
</el-col>
</el-row>
<!--日期 & 金额-->
<el-row>
<el-col :span="12">
<!--开票日期-->
<el-form-item label="开票日期" prop="dating">
<el-date-picker type="date" placeholder="选择日期" v-model="focusRow.dating" style="width: 100%;" value-format="yyyy-MM-dd"> </el-date-picker>
</el-form-item>
</el-col>
<el-col :span="1">
<!--金额-->
<el-form-item label="开票金额" prop="amount">
<el-input-number v-model="focusRow.amount" :precision="2" :step="100" controls-position="right" :min="0" :max="5000"></el-input-number>
</el-form-item>
</el-col>
</el-row>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="updateItem('editForm')" :disabled="disabled">确 定</el-button>
<!-- <el-button type="primary" @click="updateItem()" >确 定</el-button> -->
<el-button @click="resetForm('editForm')">重置</el-button>
</div>
</el-dialog>
</div>
</div>
</template>
<script>
import { addPost, fetchPosts, getPost, fetchTitles, fetchMerchants, newBill } from "../api/basic.js";
export default {
name: "Bill",
data() {
return {
isAddShow: false,
isEditShow: false,
posts: [],
titles: [],
merchants: [],
addBtnDisabled: false,
editBtnDisabled: false,
disabled: false,
// focusRow: { index: null,dating: "2019-05-28", titleId: null, titleAbbr: "上海沐融",titleName:"",taxNo:"",merchantId: null, merchantAbbr: "华容月",amount: 0.0 },
focusRow: {},
bill: {
postId: "",
// postAddress: "",
postContact : "",
// postMobile : "",
postId: "",
// postAddress: "",
billItems: [
// { dating: "2019-05-28", titleId: null, titleAbbr: "上海沐融",titleName:"",taxNo: "" ,merchantId: null, merchantAbbr: "华容月",amount: 0.0 },
// { dating: "2019-05-28", titleId: null, titleAbbr: "上海沐融",titleName:"",taxNo: "" ,merchantId: null, merchantAbbr: "华容月",amount: 0.0 },
// { dating: "2019-05-28", titleId: null, titleAbbr: "上海沐融",merchantId: null, merchantAbbr: "华容月",amount: 0.0 },
// { dating: "2019-05-28", titleId: null, titleAbbr: "上海沐融",merchantId: null, merchantAbbr: "华容月",amount: 0.0 },
// { dating: "2019-05-28", titleId: null, titleAbbr: "上海沐融",merchantId: null, merchantAbbr: "华容月",amount: 0.0 },
],
},
rules: {
postContact: [
{ required: true, message: "请选择邮寄地址",trigger: "change"},
// { min: 3, max: 15, message: "长度在 3 到 15 个字符", trigger: "blur" }
],
merchantAbbr: [
{ required: true, message: "请选择餐饮商户", trigger: "change"},
],
titleAbbr: [
{ required: true, message: "请选择抬头", trigger: "change" },
],
dating: [
{ required: true, message: "选择日期", trigger: "blur" },
],
amount: [
{ required: true, message: "请输入金额", trigger: "blur" },
{ type: 'number', message: '金额必须为数字值'}
]
}
};
},
methods: {
resetForm(formName) {
this.$refs[formName].resetFields();
// this.disabled = false;
// this.clearFocusRow();
},
/** 在表格中添加明细 */
addItem(formName) {
var oriForm = formName;
this.$refs[formName].validate((valid) => {
if (valid) {
console.info('addItem');
console.info('focusRow:' + JSON.stringify(this.focusRow));
console.info('this.bill.billItems:' + JSON.stringify(this.bill.billItems));
this.bill.billItems.splice(0,0,this.focusRow);
console.info('this.bill.billItems:' + JSON.stringify(this.bill.billItems));
this.clearFocusRow();
console.info('focusRow:' + JSON.stringify(this.focusRow));
this.isAddShow = false;
// this.resetForm(oriForm);
} else {
console.log('error submit!!');
return false;
}
});
},
/** 在表格中添加明细 */
updateItem(formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
console.info('index:' + index);
console.info('updateItem');
console.info('focusRow:' + JSON.stringify(this.focusRow));
console.info('this.bill.billItems:' + JSON.stringify(this.bill.billItems));
var index = this.focusRow.index;
this.bill.billItems.splice(index,1,this.focusRow);
console.info('this.bill.billItems:' + JSON.stringify(this.bill.billItems));
this.clearFocusRow();
console.info('focusRow:' + JSON.stringify(this.focusRow));
this.isEditShow = false;
} else {
console.log('error submit!!');
return false;
}
});
},
/** 表格-编辑一行 */
handleEdit(index, row) {
this.focusRow = row;
this.focusRow.index = index;
console.log(index, row);
this.isEditShow = true;
},
handleDelete(index, row) {
console.log(index, row);
row.splice(index, 1);
},
onSubmit(formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
console.info("bill:" + JSON.stringify(this.bill));
newBill(this.bill).then(function(response){
console.info("response:" + response)
})
this.disabled = true;
this.$message({ message: "提交成功", type: "success" });
} else {
console.log('error submit!!');
return false;
}
});
},
reset(formName) {
// this.$refs[formName].resetFields();
// this.disabled = false;
},
// querySearch(queryString, cb) {
// var posts = this.posts;
// console.error("posts:" + posts);
// var results = queryString ? posts.filter(this.createFilter(queryString)) : posts;
// // 调用 callback 返回建议列表的数据
// cb(results);
// },
/** AutoComplete Posts 处理*/
querySearchAsyncPosts(queryString, cb) {
var list = [{}];
fetchPosts().then(function(response){
for(let i of response.data){
i.value = i.contact; //将想要展示的数据作为value
// i.value = i.contact; //将想要展示的数据作为value
}
list = response.data;
cb(list);
})
// var posts = this.posts;
// console.error("posts:" + posts);
// var results = queryString ? posts.filter(this.createFilter(queryString)) : posts;
// // 调用 callback 返回建议列表的数据
// cb(results);
},
createFilterPosts(queryString) {
return posts => {
return (
posts.contact.toLowerCase().indexOf(queryString.toLowerCase()) === 0
);
};
},
loadPosts() {
console.error('loadPosts');
},
handleSelectPosts(item) {
this.bill.postId = item.id;
console.log('postId:' + this.bill.postId );
console.log('postContact:' + this.bill.postContact );
},
handleIconClickPosts(ev) {
console.log(ev);
},
/** AutoComplete Titles 处理*/
querySearchAsyncTitles(queryString, cb) {
var list = [{}];
fetchTitles().then(function(response){
console.info(response)
for(let i of response.data){
i.value = i.abbr; // 将想要展示的数据作为value
}
list = response.data;
cb(list);
})
},
createFilterTitles(queryString) {
return titles => {
return (
titles.abbr.toLowerCase().indexOf(queryString.toLowerCase()) === 0
);
};
},
loadTitles() {
console.error('loadTitles');
},
handleSelectTitles(item) {
this.focusRow.titleId = item.id;
this.focusRow.titleName = item.name;
this.focusRow.taxNo = item.taxNo;
console.log('titleId:' + this.focusRow.titleId );
console.log('titleAbbr:' + this.focusRow.titleAbbr );
console.log('titleName:' + this.focusRow.titleName );
},
handleIconClickTitles(ev) {
console.log(ev);
},
/** AutoComplete Merchant 处理*/
querySearchAsyncMerchant(queryString, cb) {
var list = [{}];
fetchMerchants().then(function(response){
for(let i of response.data){
i.value = i.abbr; // 将想要展示的数据作为value
}
list = response.data;
cb(list);
})
},
createFilterMerchant(queryString) {
return merchants => {
return (
merchants.contact.toLowerCase().indexOf(queryString.toLowerCase()) === 0
);
};
},
handleSelectMerchant(item) {
this.focusRow.merchantId = item.id;
this.focusRow.merchantsAbbr = item.abbr;
console.log('merchantId:' + this.focusRow.merchantId );
console.log('merchantsAbbr:' + this.focusRow.merchantsAbbr );
console.log('merchantsName:' + item.name );
},
handleIconClickMerchant(ev) {
console.log(ev);
},
loadMerchant() {
console.error('loadTitles');
},
/** 清理当前缓存 */
clearFocusRow() {
this.focusRow = {}
console.info('clearFocusRow');
},
},
mounted() {
// this.posts = this.loadPosts();
// console.info('mounted.this.posts:' + posts);
}
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.subtitle{
font-size: 12px;
color: #b4b4b4;
}
h1,
h2 {
font-weight: normal;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
.el-form-item{
/* width: 200px; */
/* margin: 10px; */
float:inherit ;
padding: 20px;
}
.group {
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
/* box-shadow: 0 2px 4px rgba(0, 0, 0, 0.12), 0 0 6px rgba(0, 0, 0, 0.04); */
text-align: center;
width: 800px;
/* border-inline-width: 80px; */
/* padding: 24px; */
border-radius: 8px;
align-self: center;
text-align: left;
margin: auto;
/* position: absolute; */
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.gTable{
/* box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); */
/* box-shadow: 0 2px 4px rgba(0, 0, 0, 0.12), 0 0 6px rgba(0, 0, 0, 0.04); */
text-align: left;
width: 700px;
/* border-inline-width: 80px; */
/* padding: 24px; */
/* border-radius: 8px; */
align-self: left;
margin-left: 24px;
/* margin: auto; */
/* position: absolute; */
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.itemBtn {
width: 100px;
float:right;
}
.el-form {
/* box-shadow: 0 2px 4px rgba(0, 0, 0, 0.12), 0 0 6px rgba(0, 0, 0, 0.04); */
/* border-inline-width: 80px; */
/* border-inline-width: 200px; */
padding: 24px;
border-radius: 4px;
/* align-self: center; */
align-self: center;
text-align: left;
/* width: 800px; */
}
.posts,.titles,.merchants{
width: 487px;
}
.el-form-item {
margin-bottom: 0px;
}
.submit {
width: 100px;
}
</style>
+531
View File
@@ -0,0 +1,531 @@
<template>
<div class="main">
<div class="group">
<!-- <el-form ref="form" :model="form" :rules="rules" label-width="80px"> -->
<!-- <el-form ref="form" :model="form" label-width="800px"> -->
<el-form ref="billForm" :model="bill" :rules="rules" label-width="80px">
<el-form-item label="邮寄地址" prop="postContact">
<!--邮寄地址-->
<el-autocomplete
class="posts"
popper-class="my-autocomplete"
v-model="bill.postContact"
:fetch-suggestions="querySearchAsyncPosts"
placeholder="请输选择邮寄地址"
@select="handleSelectPosts" :disabled="disabled">
<i class="el-icon-edit el-input__icon" slot="suffix" @click="handleIconClickPosts"></i>
<template slot-scope="{ item }">
<div class="name">{{ item.contact }}</div>
<span class="subtitle">{{ item.address }}</span>
<span class="subtitle">;</span>
<span class="subtitle">{{ item.mobile }}</span>
<span class="subtitle">;</span>
</template>
</el-autocomplete>
</el-form-item>
<div class="gTable">
<!--增删改查-->
<!--明细-->
<!-- <el-form-item label="明细" prop="username"> -->
<!--分割线-->
<!-- <span><el-divider></el-divider></span> -->
<el-table :data="bill.billItems" height="400px" style="width: 100%">
<el-table-column prop="dating" label="日期" width="100" ></el-table-column>
<el-table-column prop="titleAbbr" label="抬头" width="150">
<template slot-scope="scope">
<el-popover trigger="hover" placement="top">
<p>税号: {{ scope.row.taxNo }}</p>
<p>抬头: {{ scope.row.titleName }}</p>
<div slot="reference" class="name-wrapper">
<el-tag size="medium">{{ scope.row.titleAbbr }}</el-tag>
</div>
</el-popover>
</template>
</el-table-column>
<el-table-column prop="merchantAbbr" label="餐饮商户" width="150" ></el-table-column>
<el-table-column prop="amount" label="金额" width="80" ></el-table-column>
<el-table-column align="right" >
<template slot="header" slot-scope="scope">
<!-- <el-button class="itemBtn" type="primary" size="medium" @click="clearFocusRow();isAddShow = true;resetForm('addForm');" :disabled="disabled">添加</el-button> -->
<el-button class="itemBtn" type="primary" size="small" @click="clearFocusRow();isAddShow = true;resetForm('addForm');" :disabled="disabled">添加</el-button>
</template>
<template slot-scope="scope">
<el-button-group>
<!-- type="success" -->
<el-button
size="mini"
icon="el-icon-edit"
@click="handleEdit(scope.$index, scope.row)" :disabled="disabled">编辑</el-button>
<!-- type="danger" -->
<el-button
size="mini"
icon="el-icon-delete"
@click.native.prevent="handleDelete(scope.$index,bill.billItems)" :disabled="disabled">删除</el-button>
</el-button-group>
</template>
</el-table-column>
</el-table>
<!-- </el-form-item> -->
</div>
<el-form-item label-width="2px">
<el-button class="submit" type="primary" @click="onSubmit('billForm')" :disabled="disabled">提交</el-button>
</el-form-item>
</el-form>
</div>
<!--增加-->
<div width="600px">
<el-dialog title="增加" :visible.sync="isAddShow" width="700px" >
<el-form ref="addForm" :model="focusRow" :rules="rules" label-width="80px">
<el-row>
<el-col :span="24">
<!--抬头-->
<el-form-item label="抬头简称" prop="titleAbbr">
<el-autocomplete class="titles" popper-class="my-autocomplete" v-model="focusRow.titleAbbr" :fetch-suggestions="querySearchAsyncTitles" placeholder="请输选择抬头" @select="handleSelectTitles" >
<i class="el-icon-edit el-input__icon" slot="suffix" @click="handleIconClickTitles" ></i>
<template slot-scope="{ item }">
<div class="name">{{ item.abbr }}</div>
<span class="subtitle">{{ item.name }}</span>
<span class="subtitle">{{ item.taxNo }}</span>
</template>
</el-autocomplete>
</el-form-item>
</el-col>
</el-row>
<!--商户-->
<el-row>
<el-col :span="24">
<el-form-item label="开票商家" prop="merchantAbbr">
<el-autocomplete class="merchants" v-model="focusRow.merchantAbbr" :fetch-suggestions="querySearchAsyncMerchant" placeholder="请选择餐饮商户" @select="handleSelectMerchant">
<i class="el-icon-edit el-input__icon" slot="suffix" @click="handleIconClickTitles" ></i>
<template slot-scope="{ item }">
<div class="name">{{ item.abbr }}</div>
<span class="subtitle">{{ item.name }}</span>
</template>
</el-autocomplete>
</el-form-item>
</el-col>
</el-row>
<!--日期 & 金额-->
<el-row>
<el-col :span="12">
<!--开票日期-->
<el-form-item label="开票日期" prop="dating">
<el-date-picker type="date" placeholder="选择日期" v-model="focusRow.dating" style="width: 100%;" value-format="yyyy-MM-dd"> </el-date-picker>
</el-form-item>
</el-col>
<el-col :span="1">
<!--金额-->
<el-form-item label="开票金额" prop="amount">
<el-input-number v-model="focusRow.amount" :precision="2" :step="100" controls-position="right" :min="0" :max="5000"></el-input-number>
</el-form-item>
</el-col>
</el-row>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="addItem('addForm')" :disabled="disabled">确 定</el-button>
<el-button @click="resetForm('addForm')">重置</el-button>
</div>
</el-dialog>
</div>
<!--编辑-->
<div width="600px">
<el-dialog title="编辑" :visible.sync="isEditShow" width="700px" >
<el-form ref="editForm" :model="focusRow" :rules="rules" label-width="80px">
<el-row>
<el-col :span="24">
<!--抬头-->
<el-form-item label="抬头简称" prop="titleAbbr">
<el-autocomplete class="titles" popper-class="my-autocomplete" v-model="focusRow.titleAbbr" :fetch-suggestions="querySearchAsyncTitles" placeholder="请输选择抬头" @select="handleSelectTitles" >
<i class="el-icon-edit el-input__icon" slot="suffix" @click="handleIconClickTitles" ></i>
<template slot-scope="{ item }">
<div class="name">{{ item.abbr }}</div>
<span class="subtitle">{{ item.name }}</span>
<span class="subtitle">{{ item.taxNo }}</span>
</template>
</el-autocomplete>
</el-form-item>
</el-col>
</el-row>
<!--商户-->
<el-row>
<el-col :span="24">
<el-form-item label="开票商家" prop="merchantAbbr">
<el-autocomplete class="merchants" v-model="focusRow.merchantAbbr" :fetch-suggestions="querySearchAsyncMerchant" placeholder="请选择餐饮商户" @select="handleSelectMerchant">
<i class="el-icon-edit el-input__icon" slot="suffix" @click="handleIconClickTitles" ></i>
<template slot-scope="{ item }">
<div class="name">{{ item.abbr }}</div>
<span class="subtitle">{{ item.name }}</span>
</template>
</el-autocomplete>
</el-form-item>
</el-col>
</el-row>
<!--日期 & 金额-->
<el-row>
<el-col :span="12">
<!--开票日期-->
<el-form-item label="开票日期" prop="dating">
<el-date-picker type="date" placeholder="选择日期" v-model="focusRow.dating" style="width: 100%;" value-format="yyyy-MM-dd"> </el-date-picker>
</el-form-item>
</el-col>
<el-col :span="1">
<!--金额-->
<el-form-item label="开票金额" prop="amount">
<el-input-number v-model="focusRow.amount" :precision="2" :step="100" controls-position="right" :min="0" :max="5000"></el-input-number>
</el-form-item>
</el-col>
</el-row>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="updateItem('editForm')" :disabled="disabled">确 定</el-button>
<!-- <el-button type="primary" @click="updateItem()" >确 定</el-button> -->
<el-button @click="resetForm('editForm')">重置</el-button>
</div>
</el-dialog>
</div>
</div>
</template>
<script>
import { addPost, fetchPosts, getPost, fetchTitles, fetchMerchants, newBill } from "../api/basic.js";
export default {
name: "Bill",
data() {
return {
isAddShow: false,
isEditShow: false,
posts: [],
titles: [],
merchants: [],
addBtnDisabled: false,
editBtnDisabled: false,
disabled: false,
// focusRow: { index: null,dating: "2019-05-28", titleId: null, titleAbbr: "上海沐融",titleName:"",taxNo:"",merchantId: null, merchantAbbr: "华容月",amount: 0.0 },
focusRow: {},
bill: {
postId: "",
// postAddress: "",
postContact : "",
// postMobile : "",
postId: "",
// postAddress: "",
billItems: [
// { dating: "2019-05-28", titleId: null, titleAbbr: "上海沐融",titleName:"",taxNo: "" ,merchantId: null, merchantAbbr: "华容月",amount: 0.0 },
// { dating: "2019-05-28", titleId: null, titleAbbr: "上海沐融",titleName:"",taxNo: "" ,merchantId: null, merchantAbbr: "华容月",amount: 0.0 },
// { dating: "2019-05-28", titleId: null, titleAbbr: "上海沐融",merchantId: null, merchantAbbr: "华容月",amount: 0.0 },
// { dating: "2019-05-28", titleId: null, titleAbbr: "上海沐融",merchantId: null, merchantAbbr: "华容月",amount: 0.0 },
// { dating: "2019-05-28", titleId: null, titleAbbr: "上海沐融",merchantId: null, merchantAbbr: "华容月",amount: 0.0 },
],
},
rules: {
postContact: [
{ required: true, message: "请选择邮寄地址",trigger: "change"},
// { min: 3, max: 15, message: "长度在 3 到 15 个字符", trigger: "blur" }
],
merchantAbbr: [
{ required: true, message: "请选择餐饮商户", trigger: "change"},
],
titleAbbr: [
{ required: true, message: "请选择抬头", trigger: "change" },
],
dating: [
{ required: true, message: "选择日期", trigger: "blur" },
],
amount: [
{ required: true, message: "请输入金额", trigger: "blur" },
{ type: 'number', message: '金额必须为数字值'}
]
}
};
},
methods: {
resetForm(formName) {
this.$refs[formName].resetFields();
// this.disabled = false;
// this.clearFocusRow();
},
/** 在表格中添加明细 */
addItem(formName) {
var oriForm = formName;
this.$refs[formName].validate((valid) => {
if (valid) {
console.info('addItem');
console.info('focusRow:' + JSON.stringify(this.focusRow));
console.info('this.bill.billItems:' + JSON.stringify(this.bill.billItems));
this.bill.billItems.splice(0,0,this.focusRow);
console.info('this.bill.billItems:' + JSON.stringify(this.bill.billItems));
this.clearFocusRow();
console.info('focusRow:' + JSON.stringify(this.focusRow));
this.isAddShow = false;
// this.resetForm(oriForm);
} else {
console.log('error submit!!');
return false;
}
});
},
/** 在表格中添加明细 */
updateItem(formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
console.info('index:' + index);
console.info('updateItem');
console.info('focusRow:' + JSON.stringify(this.focusRow));
console.info('this.bill.billItems:' + JSON.stringify(this.bill.billItems));
var index = this.focusRow.index;
this.bill.billItems.splice(index,1,this.focusRow);
console.info('this.bill.billItems:' + JSON.stringify(this.bill.billItems));
this.clearFocusRow();
console.info('focusRow:' + JSON.stringify(this.focusRow));
this.isEditShow = false;
} else {
console.log('error submit!!');
return false;
}
});
},
/** 表格-编辑一行 */
handleEdit(index, row) {
this.focusRow = row;
this.focusRow.index = index;
console.log(index, row);
this.isEditShow = true;
},
handleDelete(index, row) {
console.log(index, row);
row.splice(index, 1);
},
onSubmit(formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
console.info("bill:" + JSON.stringify(this.bill));
newBill(this.bill).then(function(response){
console.info("response:" + response)
})
this.disabled = true;
this.$message({ message: "提交成功", type: "success" });
} else {
console.log('error submit!!');
return false;
}
});
},
reset(formName) {
// this.$refs[formName].resetFields();
// this.disabled = false;
},
// querySearch(queryString, cb) {
// var posts = this.posts;
// console.error("posts:" + posts);
// var results = queryString ? posts.filter(this.createFilter(queryString)) : posts;
// // 调用 callback 返回建议列表的数据
// cb(results);
// },
/** AutoComplete Posts 处理*/
querySearchAsyncPosts(queryString, cb) {
var list = [{}];
fetchPosts().then(function(response){
for(let i of response.data){
i.value = i.contact; //将想要展示的数据作为value
// i.value = i.contact; //将想要展示的数据作为value
}
list = response.data;
cb(list);
})
// var posts = this.posts;
// console.error("posts:" + posts);
// var results = queryString ? posts.filter(this.createFilter(queryString)) : posts;
// // 调用 callback 返回建议列表的数据
// cb(results);
},
createFilterPosts(queryString) {
return posts => {
return (
posts.contact.toLowerCase().indexOf(queryString.toLowerCase()) === 0
);
};
},
loadPosts() {
console.error('loadPosts');
},
handleSelectPosts(item) {
this.bill.postId = item.id;
console.log('postId:' + this.bill.postId );
console.log('postContact:' + this.bill.postContact );
},
handleIconClickPosts(ev) {
console.log(ev);
},
/** AutoComplete Titles 处理*/
querySearchAsyncTitles(queryString, cb) {
var list = [{}];
fetchTitles().then(function(response){
console.info(response)
for(let i of response.data){
i.value = i.abbr; // 将想要展示的数据作为value
}
list = response.data;
cb(list);
})
},
createFilterTitles(queryString) {
return titles => {
return (
titles.abbr.toLowerCase().indexOf(queryString.toLowerCase()) === 0
);
};
},
loadTitles() {
console.error('loadTitles');
},
handleSelectTitles(item) {
this.focusRow.titleId = item.id;
this.focusRow.titleName = item.name;
this.focusRow.taxNo = item.taxNo;
console.log('titleId:' + this.focusRow.titleId );
console.log('titleAbbr:' + this.focusRow.titleAbbr );
console.log('titleName:' + this.focusRow.titleName );
},
handleIconClickTitles(ev) {
console.log(ev);
},
/** AutoComplete Merchant 处理*/
querySearchAsyncMerchant(queryString, cb) {
var list = [{}];
fetchMerchants().then(function(response){
for(let i of response.data){
i.value = i.abbr; // 将想要展示的数据作为value
}
list = response.data;
cb(list);
})
},
createFilterMerchant(queryString) {
return merchants => {
return (
merchants.contact.toLowerCase().indexOf(queryString.toLowerCase()) === 0
);
};
},
handleSelectMerchant(item) {
this.focusRow.merchantId = item.id;
this.focusRow.merchantsAbbr = item.abbr;
console.log('merchantId:' + this.focusRow.merchantId );
console.log('merchantsAbbr:' + this.focusRow.merchantsAbbr );
console.log('merchantsName:' + item.name );
},
handleIconClickMerchant(ev) {
console.log(ev);
},
loadMerchant() {
console.error('loadTitles');
},
/** 清理当前缓存 */
clearFocusRow() {
this.focusRow = {}
console.info('clearFocusRow');
},
},
mounted() {
// this.posts = this.loadPosts();
// console.info('mounted.this.posts:' + posts);
}
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.subtitle{
font-size: 12px;
color: #b4b4b4;
}
h1,
h2 {
font-weight: normal;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
.el-form-item{
/* width: 200px; */
/* margin: 10px; */
float:inherit ;
padding: 20px;
}
.group {
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
/* box-shadow: 0 2px 4px rgba(0, 0, 0, 0.12), 0 0 6px rgba(0, 0, 0, 0.04); */
text-align: center;
width: 800px;
/* border-inline-width: 80px; */
/* padding: 24px; */
border-radius: 8px;
align-self: center;
text-align: left;
margin: auto;
/* position: absolute; */
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.gTable{
/* box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); */
/* box-shadow: 0 2px 4px rgba(0, 0, 0, 0.12), 0 0 6px rgba(0, 0, 0, 0.04); */
text-align: left;
width: 700px;
/* border-inline-width: 80px; */
/* padding: 24px; */
/* border-radius: 8px; */
align-self: left;
margin-left: 24px;
/* margin: auto; */
/* position: absolute; */
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.itemBtn {
width: 100px;
float:right;
}
.el-form {
/* box-shadow: 0 2px 4px rgba(0, 0, 0, 0.12), 0 0 6px rgba(0, 0, 0, 0.04); */
/* border-inline-width: 80px; */
/* border-inline-width: 200px; */
padding: 24px;
border-radius: 4px;
/* align-self: center; */
align-self: center;
text-align: left;
/* width: 800px; */
}
.posts,.titles,.merchants{
width: 487px;
}
.el-form-item {
margin-bottom: 0px;
}
.submit {
width: 100px;
}
</style>
+164
View File
@@ -0,0 +1,164 @@
<template>
<div class="login">
<div class="group">
<el-form ref="ruleForm" :model="ruleForm" :rules="rules" label-width="80px">
<el-form-item label="用户名" prop="username">
<el-input v-model="ruleForm.username" clearable placeholder="请输入用户名"></el-input>
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input v-model="ruleForm.password" @keyup.enter.native="onSubmit('ruleForm')" show-password placeholder="请输入密码"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit('ruleForm')" :disabled="disabled">登录</el-button>
<el-button @click="reset('ruleForm')">重置</el-button>
</el-form-item>
</el-form>
</div>
</div>
</template>
<script>
import { UserLoginAction } from "../api/user.js";
export default {
name: "Login",
data() {
return {
disabled: false,
ruleForm: {
username: "",
password: ""
},
rules: {
username: [
{ required: true, message: "请输入户名", trigger: "blur" },
{ min: 3, max: 15, message: "长度在 3 到 15 个字符", trigger: "blur" }
],
password: [
{ required: true, message: "请输入密码", trigger: "change" },
{ min: 3, max: 15, message: "长度在 3 到 15 个字符", trigger: "blur" }
]
}
};
},
methods: {
onSubmit(formName) {
this.$refs[formName].validate(valid => {
var _this = this;
if (valid) {
UserLoginAction(this.ruleForm)
.then(res => {
var token = "Bearer " + res.data.token;
console.log("token:" + token);
window.localStorage.setItem("token", token);
this.disabled = true;
this.$message({ message: "登录成功", type: "success" });
})
.catch(function(error) {
// _this.$message({ message: error.response.statusText, type: 'error' });
// _this.$message.error();
if (error.response) {
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
_this.$message({ message: error.response.data.message, type: 'error' });
console.log(error.response.data);
console.log(error.response.status);
console.log(error.response.headers);
} else if (error.request) {
// The request was made but no response was received
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
// http.ClientRequest in node.js
// _this.$message.error(error.request);
console.log(error.request);
_this.$message.error('未知错误');
} else {
// Something happened in setting up the request that triggered an Error
console.log("Error", error.message);
_this.$message.error(error.response.message);
}
console.log(error.config);
// _this.$message.error(error.response.statusText);
});
} else {
console.log("error submit!!");
return false;
}
});
},
reset(formName) {
this.$refs[formName].resetFields();
this.disabled = false;
}
}
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h1,
h2 {
font-weight: normal;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
.group {
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
/* box-shadow: 0 2px 4px rgba(0, 0, 0, 0.12), 0 0 6px rgba(0, 0, 0, 0.04); */
text-align: center;
width: 500px;
/* border-inline-width: 80px; */
/* padding: 24px; */
border-radius: 8px;
align-self: center;
text-align: left;
margin: auto;
/* position: absolute; */
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.el-checkbox-group {
width: 320px;
float: left;
margin: 0;
padding: 0;
list-style: none;
}
.el-checkbox {
float: left;
width: 160px;
margin: 0;
padding: 0;
}
.el-radio-group {
width: 320px;
/* float:left; */
margin: 0;
padding: 0;
list-style: none;
}
.el-radio {
float: left;
width: 160px;
margin: 0;
padding: 0;
}
.el-form {
/* box-shadow: 0 2px 4px rgba(0, 0, 0, 0.12), 0 0 6px rgba(0, 0, 0, 0.04); */
border-inline-width: 80px;
padding: 24px;
border-radius: 4px;
align-self: center;
text-align: left;
width: 350px;
}
</style>
+134
View File
@@ -0,0 +1,134 @@
<template>
<div class="merchant">
<div class="group">
<!-- <form class="el-form bill"> -->
<el-form ref="form" :model="form" label-width="80px">
<el-form-item label="商户简称">
<el-input v-model="merc.abbr"></el-input>
</el-form-item>
<el-form-item label="商户全称">
<el-input v-model="merc.name"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit">立即创建</el-button>
<el-button>取消</el-button>
</el-form-item>
</el-form>
<!-- </form> -->
</div>
</div>
</template>
<script>
import { addMerchant, fetchMerchants, getMerchant } from "../api/basic.js";
export default {
name: "Merchant",
data() {
return {
merc: {
name: "",
abbr: ""
}
};
},
methods: {
onSubmit() {
console.log("submit!");
console.log("merc:" + this.merc.name);
// MerchantAddAction(JSON.stringify(this.merc))
addMerchant(this.merc).then(res => {
var merchantId = res.data.id;
console.log("res.data.code" + res.data.code);
this.$message({ message: "恭喜你,这是一条成功消息", type: "success" });
getMerchant(merchantId);
});
}
}
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h1,
h2 {
font-weight: normal;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
.group {
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
/* box-shadow: 0 2px 4px rgba(0, 0, 0, 0.12), 0 0 6px rgba(0, 0, 0, 0.04); */
text-align: center;
width: 600px;
/* border-inline-width: 80px; */
/* padding: 24px; */
border-radius: 8px;
align-self: center;
text-align: left;
margin: auto;
/* position: absolute; */
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.merchant {
/* margin: 0 auto; */
/* text-align: center; */
/* background-color: #fff; */
/* border-radius: 20px; */
/* width: 300px;
height: 350px; */
/* margin: auto; */
/* position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0; */
}
.el-checkbox-group {
width: 320px;
float: left;
margin: 0;
padding: 0;
list-style: none;
}
.el-checkbox {
float: left;
width: 160px;
margin: 0;
padding: 0;
}
.el-radio-group {
width: 320px;
/* float:left; */
margin: 0;
padding: 0;
list-style: none;
}
.el-radio {
float: left;
width: 160px;
margin: 0;
padding: 0;
}
.el-form {
/* box-shadow: 0 2px 4px rgba(0, 0, 0, 0.12), 0 0 6px rgba(0, 0, 0, 0.04); */
border-inline-width: 80px;
padding: 24px;
border-radius: 4px;
align-self: center;
text-align: left;
width: 480px;
}
</style>
+168
View File
@@ -0,0 +1,168 @@
<template>
<div class="merchant">
<div class="group">
<form class="el-form bill">
<el-form ref="form" :model="form" label-width="80px">
<el-form-item label="活动名称">
<el-input v-model="form.name"></el-input>
</el-form-item>
<el-form-item label="活动区域">
<el-select v-model="form.region" placeholder="请选择活动区域">
<el-option label="区域一" value="shanghai"></el-option>
<el-option label="区域二" value="beijing"></el-option>
</el-select>
</el-form-item>
<el-form-item label="活动时间">
<el-col :span="11">
<el-date-picker
type="date"
placeholder="选择日期"
v-model="form.date1"
style="width: 100%;"
></el-date-picker>
</el-col>
<el-col class="line" :span="2">-</el-col>
<el-col :span="11">
<el-time-picker placeholder="选择时间" v-model="form.date2" style="width: 100%;"></el-time-picker>
</el-col>
</el-form-item>
<el-form-item label="即时配送">
<el-switch v-model="form.delivery"></el-switch>
</el-form-item>
<el-form-item label="活动性质">
<el-checkbox-group v-model="form.type">
<el-checkbox label="美食/餐厅线上活动" name="type"></el-checkbox>
<el-checkbox label="地推活动" name="type"></el-checkbox>
<el-checkbox label="线下主题活动" name="type"></el-checkbox>
<el-checkbox label="单纯品牌曝光" name="type"></el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="特殊资源">
<el-radio-group v-model="form.resource">
<el-radio label="线上品牌商赞助"></el-radio>
<el-radio label="线下场地免费"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="活动形式">
<el-input type="textarea" v-model="form.desc"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit">立即创建</el-button>
<el-button>取消</el-button>
</el-form-item>
</el-form>
</form>
</div>
</div>
</template>
<script>
export default {
name: "Merchant",
data() {
return {
form: {
name: "",
region: "",
date1: "",
date2: "",
delivery: false,
type: [],
resource: "",
desc: ""
}
};
},
methods: {
onSubmit() {
console.log("submit!");
}
}
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h1,
h2 {
font-weight: normal;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
.group {
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
/* box-shadow: 0 2px 4px rgba(0, 0, 0, 0.12), 0 0 6px rgba(0, 0, 0, 0.04); */
text-align: center;
width: 600px;
/* border-inline-width: 80px; */
/* padding: 24px; */
border-radius: 8px;
align-self: center;
text-align: left;
margin: auto;
/* position: absolute; */
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.merchant {
/* margin: 0 auto; */
/* text-align: center; */
/* background-color: #fff; */
/* border-radius: 20px; */
/* width: 300px;
height: 350px; */
/* margin: auto; */
/* position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0; */
}
.el-checkbox-group {
width: 320px;
float:left;
margin: 0;
padding: 0;
list-style: none;
}
.el-checkbox{
float: left;
width: 160px;
margin: 0;
padding: 0;
}
.el-radio-group {
width: 320px;
/* float:left; */
margin: 0;
padding: 0;
list-style: none;
}
.el-radio{
float: left;
width: 160px;
margin: 0;
padding: 0;
}
.el-form {
/* box-shadow: 0 2px 4px rgba(0, 0, 0, 0.12), 0 0 6px rgba(0, 0, 0, 0.04); */
border-inline-width: 80px;
padding: 24px;
border-radius: 4px;
align-self: center;
text-align: left;
width: 480px;
}
</style>
View File
@@ -0,0 +1,27 @@
// A custom Nightwatch assertion.
// The assertion name is the filename.
// Example usage:
//
// browser.assert.elementCount(selector, count)
//
// For more information on custom assertions see:
// http://nightwatchjs.org/guide#writing-custom-assertions
exports.assertion = function (selector, count) {
this.message = 'Testing if element <' + selector + '> has count: ' + count
this.expected = count
this.pass = function (val) {
return val === this.expected
}
this.value = function (res) {
return res.value
}
this.command = function (cb) {
var self = this
return this.api.execute(function (selector) {
return document.querySelectorAll(selector).length
}, [selector], function (res) {
cb.call(self, res)
})
}
}
@@ -0,0 +1,46 @@
require('babel-register')
var config = require('../../config')
// http://nightwatchjs.org/gettingstarted#settings-file
module.exports = {
src_folders: ['test/e2e/specs'],
output_folder: 'test/e2e/reports',
custom_assertions_path: ['test/e2e/custom-assertions'],
selenium: {
start_process: true,
server_path: require('selenium-server').path,
host: '127.0.0.1',
port: 4444,
cli_args: {
'webdriver.chrome.driver': require('chromedriver').path
}
},
test_settings: {
default: {
selenium_port: 4444,
selenium_host: 'localhost',
silent: true,
globals: {
devServerURL: 'http://localhost:' + (process.env.PORT || config.dev.port)
}
},
chrome: {
desiredCapabilities: {
browserName: 'chrome',
javascriptEnabled: true,
acceptSslCerts: true
}
},
firefox: {
desiredCapabilities: {
browserName: 'firefox',
javascriptEnabled: true,
acceptSslCerts: true
}
}
}
}
+48
View File
@@ -0,0 +1,48 @@
// 1. start the dev server using production config
process.env.NODE_ENV = 'testing'
const webpack = require('webpack')
const DevServer = require('webpack-dev-server')
const webpackConfig = require('../../build/webpack.prod.conf')
const devConfigPromise = require('../../build/webpack.dev.conf')
let server
devConfigPromise.then(devConfig => {
const devServerOptions = devConfig.devServer
const compiler = webpack(webpackConfig)
server = new DevServer(compiler, devServerOptions)
const port = devServerOptions.port
const host = devServerOptions.host
return server.listen(port, host)
})
.then(() => {
// 2. run the nightwatch test suite against it
// to run in additional browsers:
// 1. add an entry in test/e2e/nightwatch.conf.js under "test_settings"
// 2. add it to the --env flag below
// or override the environment flag, for example: `npm run e2e -- --env chrome,firefox`
// For more information on Nightwatch's config file, see
// http://nightwatchjs.org/guide#settings-file
let opts = process.argv.slice(2)
if (opts.indexOf('--config') === -1) {
opts = opts.concat(['--config', 'test/e2e/nightwatch.conf.js'])
}
if (opts.indexOf('--env') === -1) {
opts = opts.concat(['--env', 'chrome'])
}
const spawn = require('cross-spawn')
const runner = spawn('./node_modules/.bin/nightwatch', opts, { stdio: 'inherit' })
runner.on('exit', function (code) {
server.close()
process.exit(code)
})
runner.on('error', function (err) {
server.close()
throw err
})
})
+19
View File
@@ -0,0 +1,19 @@
// For authoring Nightwatch tests, see
// http://nightwatchjs.org/guide#usage
module.exports = {
'default e2e tests': function (browser) {
// automatically uses dev Server port from /config.index.js
// default: http://localhost:8080
// see nightwatch.conf.js
const devServer = browser.globals.devServerURL
browser
.url(devServer)
.waitForElementVisible('#app', 5000)
.assert.elementPresent('.hello')
.assert.containsText('h1', 'Welcome to Your Vue.js App')
.assert.elementCount('img', 1)
.end()
}
}
+7
View File
@@ -0,0 +1,7 @@
{
"env": {
"jest": true
},
"globals": {
}
}
+30
View File
@@ -0,0 +1,30 @@
const path = require('path')
module.exports = {
rootDir: path.resolve(__dirname, '../../'),
moduleFileExtensions: [
'js',
'json',
'vue'
],
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1'
},
transform: {
'^.+\\.js$': '<rootDir>/node_modules/babel-jest',
'.*\\.(vue)$': '<rootDir>/node_modules/vue-jest'
},
testPathIgnorePatterns: [
'<rootDir>/test/e2e'
],
snapshotSerializers: ['<rootDir>/node_modules/jest-serializer-vue'],
setupFiles: ['<rootDir>/test/unit/setup'],
mapCoverage: true,
coverageDirectory: '<rootDir>/test/unit/coverage',
collectCoverageFrom: [
'src/**/*.{js,vue}',
'!src/main.js',
'!src/router/index.js',
'!**/node_modules/**'
]
}
+3
View File
@@ -0,0 +1,3 @@
import Vue from 'vue'
Vue.config.productionTip = false
@@ -0,0 +1,11 @@
import Vue from 'vue'
import HelloWorld from '@/components/HelloWorld'
describe('HelloWorld.vue', () => {
it('should render correct contents', () => {
const Constructor = Vue.extend(HelloWorld)
const vm = new Constructor().$mount()
expect(vm.$el.querySelector('.hello h1').textContent)
.toEqual('Welcome to Your Vue.js App')
})
})