Commit fcef6617 by zhangmeng

marketing init

parents
{
"presets": [
["env", {
"modules": false,
"targets": {
"browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
}
}],
"stage-2"
],
"plugins": ["transform-vue-jsx", "transform-runtime"]
}
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
.DS_Store
node_modules/
/dist/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
// 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": {}
}
}
# marketing1
> A Vue.js project
## 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
```
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).
'use strict'
require('./check-versions')()
process.env.NODE_ENV = 'production'
const ora = require('ora')
const rm = require('rimraf')
const path = require('path')
const chalk = require('chalk')
const webpack = require('webpack')
const config = require('../config')
const webpackConfig = require('./webpack.prod.conf')
const spinner = ora('building for production...')
spinner.start()
rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
if (err) throw err
webpack(webpackConfig, (err, stats) => {
spinner.stop()
if (err) throw err
process.stdout.write(stats.toString({
colors: true,
modules: false,
children: false, // If you are using ts-loader, setting this to true will make TypeScript errors show up during build.
chunks: false,
chunkModules: false
}) + '\n\n')
if (stats.hasErrors()) {
console.log(chalk.red(' Build failed with errors.\n'))
process.exit(1)
}
console.log(chalk.cyan(' Build complete.\n'))
console.log(chalk.yellow(
' Tip: built files are meant to be served over an HTTP server.\n' +
' Opening index.html over file:// won\'t work.\n'
))
})
})
'use strict'
const chalk = require('chalk')
const semver = require('semver')
const packageConfig = require('../package.json')
const shell = require('shelljs')
function exec (cmd) {
return require('child_process').execSync(cmd).toString().trim()
}
const versionRequirements = [
{
name: 'node',
currentVersion: semver.clean(process.version),
versionRequirement: packageConfig.engines.node
}
]
if (shell.which('npm')) {
versionRequirements.push({
name: 'npm',
currentVersion: exec('npm --version'),
versionRequirement: packageConfig.engines.npm
})
}
module.exports = function () {
const warnings = []
for (let i = 0; i < versionRequirements.length; i++) {
const mod = versionRequirements[i]
if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
warnings.push(mod.name + ': ' +
chalk.red(mod.currentVersion) + ' should be ' +
chalk.green(mod.versionRequirement)
)
}
}
if (warnings.length) {
console.log('')
console.log(chalk.yellow('To use this template, you must update following to modules:'))
console.log()
for (let i = 0; i < warnings.length; i++) {
const warning = warnings[i]
console.log(' ' + warning)
}
console.log()
process.exit(1)
}
}
'use strict'
const path = require('path')
const config = require('../config')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const packageConfig = require('../package.json')
exports.assetsPath = function (_path) {
const assetsSubDirectory = process.env.NODE_ENV === 'production'
? config.build.assetsSubDirectory
: config.dev.assetsSubDirectory
return path.posix.join(assetsSubDirectory, _path)
}
exports.cssLoaders = function (options) {
options = options || {}
const cssLoader = {
loader: 'css-loader',
options: {
sourceMap: options.sourceMap
}
}
const postcssLoader = {
loader: 'postcss-loader',
options: {
sourceMap: options.sourceMap
}
}
// generate loader string to be used with extract text plugin
function generateLoaders (loader, loaderOptions) {
const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader]
if (loader) {
loaders.push({
loader: loader + '-loader',
options: Object.assign({}, loaderOptions, {
sourceMap: options.sourceMap
})
})
}
// Extract CSS when that option is specified
// (which is the case during production build)
if (options.extract) {
return ExtractTextPlugin.extract({
use: loaders,
fallback: 'vue-style-loader'
})
} else {
return ['vue-style-loader'].concat(loaders)
}
}
// https://vue-loader.vuejs.org/en/configurations/extract-css.html
return {
css: generateLoaders(),
postcss: generateLoaders(),
less: generateLoaders('less'),
sass: generateLoaders('sass', { indentedSyntax: true }),
scss: generateLoaders('sass'),
stylus: generateLoaders('stylus'),
styl: generateLoaders('stylus')
}
}
// Generate loaders for standalone style files (outside of .vue)
exports.styleLoaders = function (options) {
const output = []
const loaders = exports.cssLoaders(options)
for (const extension in loaders) {
const loader = loaders[extension]
output.push({
test: new RegExp('\\.' + extension + '$'),
use: loader
})
}
return output
}
exports.createNotifierCallback = () => {
const notifier = require('node-notifier')
return (severity, errors) => {
if (severity !== 'error') return
const error = errors[0]
const filename = error.file && error.file.split('!').pop()
notifier.notify({
title: packageConfig.name,
message: severity + ': ' + error.name,
subtitle: filename || '',
icon: path.join(__dirname, 'logo.png')
})
}
}
'use strict'
const utils = require('./utils')
const config = require('../config')
const isProduction = process.env.NODE_ENV === 'production'
const sourceMapEnabled = isProduction
? config.build.productionSourceMap
: config.dev.cssSourceMap
module.exports = {
loaders: utils.cssLoaders({
sourceMap: sourceMapEnabled,
extract: isProduction
}),
cssSourceMap: sourceMapEnabled,
cacheBusting: config.dev.cacheBusting,
transformToRequire: {
video: ['src', 'poster'],
source: 'src',
img: 'src',
image: 'xlink:href'
}
}
'use strict'
const path = require('path')
const utils = require('./utils')
const config = require('../config')
const vueLoaderConfig = require('./vue-loader.conf')
function resolve (dir) {
return path.join(__dirname, '..', dir)
}
module.exports = {
context: path.resolve(__dirname, '../'),
entry: {
app: './src/main.js'
},
output: {
path: config.build.assetsRoot,
filename: '[name].js',
publicPath: process.env.NODE_ENV === 'production'
? config.build.assetsPublicPath
: config.dev.assetsPublicPath
},
resolve: {
extensions: ['.js', '.vue', '.json'],
alias: {
'vue$': 'vue/dist/vue.esm.js',
'@': resolve('src'),
}
},
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
options: vueLoaderConfig
},
{
test: /\.(css|scss)$/,
loader: 'style!css!sass',
include: [resolve('src')]
},
{
test: /\.js$/,
loader: 'babel-loader',
include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')]
},
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('img/[name].[hash:7].[ext]')
}
},
{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('media/[name].[hash:7].[ext]')
}
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
}
}
]
},
node: {
// prevent webpack from injecting useless setImmediate polyfill because Vue
// source contains it (although only uses it if it's native).
setImmediate: false,
// prevent webpack from injecting mocks to Node native modules
// that does not make sense for the client
dgram: 'empty',
fs: 'empty',
net: 'empty',
tls: 'empty',
child_process: 'empty'
}
}
'use strict'
const utils = require('./utils')
const webpack = require('webpack')
const config = require('../config')
const merge = require('webpack-merge')
const path = require('path')
const baseWebpackConfig = require('./webpack.base.conf')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
const portfinder = require('portfinder')
const HOST = process.env.HOST
const PORT = process.env.PORT && Number(process.env.PORT)
const devWebpackConfig = merge(baseWebpackConfig, {
module: {
rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true })
},
// cheap-module-eval-source-map is faster for development
devtool: config.dev.devtool,
// these devServer options should be customized in /config/index.js
devServer: {
clientLogLevel: 'warning',
historyApiFallback: {
rewrites: [
{ from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') },
],
},
hot: true,
contentBase: false, // since we use CopyWebpackPlugin.
compress: true,
host: HOST || config.dev.host,
port: PORT || config.dev.port,
open: config.dev.autoOpenBrowser,
overlay: config.dev.errorOverlay
? { warnings: false, errors: true }
: false,
publicPath: config.dev.assetsPublicPath,
proxy: config.dev.proxyTable,
quiet: true, // necessary for FriendlyErrorsPlugin
watchOptions: {
poll: config.dev.poll,
}
},
plugins: [
new webpack.DefinePlugin({
'process.env': require('../config/dev.env')
}),
new webpack.HotModuleReplacementPlugin(),
new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update.
new webpack.NoEmitOnErrorsPlugin(),
// https://github.com/ampedandwired/html-webpack-plugin
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'index.html',
inject: true
}),
// copy custom static assets
new CopyWebpackPlugin([
{
from: path.resolve(__dirname, '../static'),
to: config.dev.assetsSubDirectory,
ignore: ['.*']
}
])
]
})
module.exports = new Promise((resolve, reject) => {
portfinder.basePort = process.env.PORT || config.dev.port
portfinder.getPort((err, port) => {
if (err) {
reject(err)
} else {
// publish the new Port, necessary for e2e tests
process.env.PORT = port
// add port to devServer config
devWebpackConfig.devServer.port = port
// Add FriendlyErrorsPlugin
devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({
compilationSuccessInfo: {
messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`],
},
onErrors: config.dev.notifyOnErrors
? utils.createNotifierCallback()
: undefined
}))
resolve(devWebpackConfig)
}
})
})
'use strict'
const path = require('path')
const utils = require('./utils')
const webpack = require('webpack')
const config = require('../config')
const merge = require('webpack-merge')
const baseWebpackConfig = require('./webpack.base.conf')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
const env = require('../config/prod.env')
const webpackConfig = merge(baseWebpackConfig, {
module: {
rules: utils.styleLoaders({
sourceMap: config.build.productionSourceMap,
extract: true,
usePostCSS: true
})
},
devtool: config.build.productionSourceMap ? config.build.devtool : false,
output: {
path: config.build.assetsRoot,
filename: utils.assetsPath('js/[name].[chunkhash].js'),
chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
},
plugins: [
// http://vuejs.github.io/vue-loader/en/workflow/production.html
new webpack.DefinePlugin({
'process.env': env
}),
new UglifyJsPlugin({
uglifyOptions: {
compress: {
warnings: false
}
},
sourceMap: config.build.productionSourceMap,
parallel: true
}),
// extract css into its own file
new ExtractTextPlugin({
filename: utils.assetsPath('css/[name].[contenthash].css'),
// Setting the following option to `false` will not extract CSS from codesplit chunks.
// Their CSS will instead be inserted dynamically with style-loader when the codesplit chunk has been loaded by webpack.
// It's currently set to `true` because we are seeing that sourcemaps are included in the codesplit bundle as well when it's `false`,
// increasing file size: https://github.com/vuejs-templates/webpack/issues/1110
allChunks: true,
}),
// Compress extracted CSS. We are using this plugin so that possible
// duplicated CSS from different components can be deduped.
new OptimizeCSSPlugin({
cssProcessorOptions: config.build.productionSourceMap
? { safe: true, map: { inline: false } }
: { safe: true }
}),
// generate dist index.html with correct asset hash for caching.
// you can customize output by editing /index.html
// see https://github.com/ampedandwired/html-webpack-plugin
new HtmlWebpackPlugin({
filename: config.build.index,
template: 'index.html',
inject: true,
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true
// more options:
// https://github.com/kangax/html-minifier#options-quick-reference
},
// necessary to consistently work with multiple chunks via CommonsChunkPlugin
chunksSortMode: 'dependency'
}),
// keep module.id stable when vendor modules does not change
new webpack.HashedModuleIdsPlugin(),
// enable scope hoisting
new webpack.optimize.ModuleConcatenationPlugin(),
// split vendor js into its own file
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks (module) {
// any required modules inside node_modules are extracted to vendor
return (
module.resource &&
/\.js$/.test(module.resource) &&
module.resource.indexOf(
path.join(__dirname, '../node_modules')
) === 0
)
}
}),
// extract webpack runtime and module manifest to its own file in order to
// prevent vendor hash from being updated whenever app bundle is updated
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest',
minChunks: Infinity
}),
// This instance extracts shared chunks from code splitted chunks and bundles them
// in a separate chunk, similar to the vendor chunk
// see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk
new webpack.optimize.CommonsChunkPlugin({
name: 'app',
async: 'vendor-async',
children: true,
minChunks: 3
}),
// copy custom static assets
new CopyWebpackPlugin([
{
from: path.resolve(__dirname, '../static'),
to: config.build.assetsSubDirectory,
ignore: ['.*']
}
])
]
})
if (config.build.productionGzip) {
const CompressionWebpackPlugin = require('compression-webpack-plugin')
webpackConfig.plugins.push(
new CompressionWebpackPlugin({
asset: '[path].gz[query]',
algorithm: 'gzip',
test: new RegExp(
'\\.(' +
config.build.productionGzipExtensions.join('|') +
')$'
),
threshold: 10240,
minRatio: 0.8
})
)
}
if (config.build.bundleAnalyzerReport) {
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
webpackConfig.plugins.push(new BundleAnalyzerPlugin())
}
module.exports = webpackConfig
'use strict'
const merge = require('webpack-merge')
const prodEnv = require('./prod.env')
module.exports = merge(prodEnv, {
NODE_ENV: '"development"'
})
'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: {
'/dmApi/':{
target:'http://gicdev.demogic.com/',
changeOrigin:true,
pathRewrite:{
'^/dmApi':''
}
}
},
// Various Dev Server settings
host: 'localhost', // can be overwritten by process.env.HOST
port: 8081, // 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-
/**
* 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: '/marketing/',
/**
* Source Maps
*/
productionSourceMap: false,
// 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
}
}
'use strict'
module.exports = {
NODE_ENV: '"production"'
}
File added
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="shortcut icon" href="./static/img/favicon.ico">
<title>GIC后台</title>
<link rel="stylesheet" type="text/css" href="static/css/iconfont.css">
<link rel="stylesheet" type="text/css" href="static/css/common.css">
</head>
<body>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
{
"name": "marketing1",
"version": "1.0.0",
"description": "A Vue.js project",
"author": "",
"private": true,
"scripts": {
"dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
"start": "npm run dev",
"build": "node build/build.js",
"publish": "publish.bat"
},
"dependencies": {
"@antv/data-set": "^0.8.9",
"@antv/g2": "^3.2.6",
"@riophae/vue-treeselect": "^0.0.36",
"@tinymce/tinymce-vue": "^1.0.8",
"axios": "^0.18.0",
"echarts": "^4.1.0",
"element-ui": "^2.4.1",
"packele": "^1.0.3",
"scriptjs": "^2.5.8",
"tinymce": "^4.8.2",
"v-charts": "^1.17.8",
"viser-vue": "^2.2.5",
"vue": "^2.5.2",
"vue-axios": "^2.1.1",
"vue-qr": "^1.3.8",
"vue-router": "^3.0.1",
"vue-ueditor-wrap": "^1.3.4",
"vue2-editor": "^2.5.0",
"vuedraggable": "^2.16.0",
"vuex": "^3.0.1"
},
"devDependencies": {
"autoprefixer": "^7.1.2",
"babel-cli": "^6.26.0",
"babel-core": "^6.22.1",
"babel-helper-vue-jsx-merge-props": "^2.0.3",
"babel-loader": "^7.1.1",
"babel-plugin-syntax-jsx": "^6.18.0",
"babel-plugin-transform-runtime": "^6.22.0",
"babel-plugin-transform-vue-jsx": "^3.5.0",
"babel-preset-env": "^1.3.2",
"babel-preset-flow": "^6.23.0",
"babel-preset-stage-2": "^6.22.0",
"chalk": "^2.0.1",
"copy-webpack-plugin": "^4.0.1",
"css-loader": "^0.28.0",
"element-theme-chalk": "^2.4.1",
"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",
"localforage": "^1.7.2",
"node-notifier": "^5.1.2",
"node-sass": "^4.9.0",
"optimize-css-assets-webpack-plugin": "^3.2.0",
"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",
"sass-loader": "^7.0.3",
"semver": "^5.3.0",
"shelljs": "^0.7.6",
"uglifyjs-webpack-plugin": "^1.1.1",
"url-loader": "^0.5.8",
"vue-lazyload": "^1.2.6",
"vue-loader": "^13.3.0",
"vue-style-loader": "^3.0.1",
"vue-template-compiler": "^2.5.2",
"webpack": "^3.6.0",
"webpack-bundle-analyzer": "^2.9.0",
"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"
]
}
net use z: \\123.207.187.158\e$ "59qgo8y9vmmq" /user:"root"
rmdir /mydata/gicweb/marketing /s /q
md /mydata/gicweb/marketing
xcopy ./dist /mydata/gicweb/marketing /s /y
net use * /del /y
pause
<template>
<div id="app">
<keep-alive :include="include">
<router-view></router-view>
</keep-alive>
<lg-preview></lg-preview>
</div>
</template>
<script>
export default {
name: 'App',
data(){
return {
include:[]
}
}
}
</script>
<style lang="scss">
@import './assets/style/base/index.scss';
@import './assets/iconfont/iconfont.css';
#app{
height: 100%;
background-color: #f0f2f5;
}
</style>
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
@import './reset.scss';
@import '../components/index.scss';
#app .footlogo {
position: relative;
// padding:0;
}
#app .dm-wrap{
margin:24px;
background:#FFF;
padding:24px;
// min-height: 500px;
}
#app .dm-form__wrap{
margin:24px;
background:#FFF;
padding:20px 32px;
// min-height: 500px;
}
.sms-table_info{
&>img {
width: 60px;
height: 60px;
}
}
.bottom-border{
border-bottom: 1px solid #DCDFE6;
}
.dm-title__label {
border-bottom: 1px solid #DCDFE6;
width: calc(100% + 32px);
padding: 0 0 20px 32px;
font-size: 16px;
font-weight: 700;
margin: 0 0 20px -32px;
}
.dm-title__label--outer {
border-bottom: 1px solid #DCDFE6;
width: calc(100% + 24px);
padding: 0 0 20px 24px;
font-size: 16px;
font-weight: 700;
margin: 0 0 20px -24px;
}
.gic-people--button{
background: #f2f3f4;
padding: 0px 0px 20px 124px;
.el-button+.el-button {
margin-left: 5px;
}
}
.filter--box {
background: #ebeef5;
height: 50px;
line-height: 50px;
.el-checkbox{
background: #fff;
}
.el-checkbox.is-bordered+.el-checkbox.is-bordered {
margin-left: 0;
}
}
.no-link{
.el-breadcrumb__inner {
font-weight: 400!important;
cursor: text!important;
color: #606266!important;
}
}
::-webkit-scrollbar-track-piece {
background-color: #f0f2f5;
-webkit-border-radius: 6px;
}
::-webkit-scrollbar-button{
background-color:#e4e7ed;
}
.select-shop__tag {
margin-bottom: 5px;
}
.select-shop__tag + .select-shop__tag {
margin-left: 10px;
}
.chart--nodata {
width: auto;
text-align: center;
&::before {
content: ' ';
display: block;
width: 200px;
height: 200px;
background: url(/marketing/static/img/chart-no-data.png) no-repeat center;
background-size: cover;
margin: 20px auto 10px auto;
}
&::after {
content: '暂无数据';
color: #808995;
margin: 0 auto;
text-align:center;
font-size:13px;
display: block;
margin-bottom: 60px;
}
}
.leftBar-wrap .el-menu{
background-color: #020b21;
}
@import './var.scss';
@import '../mixin/index.scss';
input:-webkit-autofill,
textarea:-webkit-autofill,
select:-webkit-autofill {
-webkit-box-shadow: 0 0 0 1000px white inset;
}
input[type=text]:focus, input[type=password]:focus, textarea:focus {
-webkit-box-shadow: 0 0 0 1000px white inset;
}
html,body{
color: $primary-font-color;
margin: 0;
padding: 0;
font-size: 14px;
font-family: Helvetica Neue,Helvetica,PingFang SC,Hiragino Sans GB,Microsoft YaHei,\\5FAE\8F6F\96C5\9ED1,Arial,sans-serif;
}
i {
font-size:14px;
}
p {
color: $primary-font-color;
}
a {
color: #409EFF;
}
a:hover {
color: #1e6cd5;
}
.block-center{
margin: 0 auto;
}
.text-center{
text-align: center;
}
.text-right{
text-align: right;
}
.text-left{
text-align: left;
}
.container{
position: relative;
margin: 0 auto;
}
.pt5{
padding-top: 5px!important;
}
.pt10{
padding-top: 10px!important;
}
.pt20{
padding-top: 20px!important;
}
.pt100{
padding-top: 100px!important;
}
.pt200{
padding-top: 200px!important;
}
.pb10{
padding-bottom: 10px!important;
}
.pb15{
padding-bottom: 15px!important;
}
.pb20{
padding-bottom: 20px!important;
}
.pb22{
padding-bottom: 22px!important;
}
.pl5{
padding-left: 5px!important;
}
.pl10{
padding-left: 10px!important;
}
.pl20{
padding-left: 20px!important;
}
.pr5{
padding-right: 5px!important;
}
.pr7{
padding-right: 7px!important;
}
.pr10{
padding-right: 10px!important;
}
.pr20{
padding-right: 20px!important;
}
.ml30{
margin-left: 30px!important;
}
.ml80{
margin-left: 80px!important;
}
.ml100{
margin-left: 100px!important;
}
.mt20{
margin-top: 20px!important;
}
.mt10{
margin-top: 10px!important;
}
.ml5{
margin-left: 5px!important;
}
.ml10{
margin-left: 10px!important;
}
.mb10{
margin-bottom: 10px!important;
}
.mr10{
margin-right: 10px!important;
}
.mr20{
margin-right: 20px!important;
}
.mb20{
margin-bottom: 20px!important;
}
.width50{
width: 50%!important;
}
.width30{
width: 30%!important;
}
.width-auto{
width: auto!important;
}
.w80{
width: 80px!important;
}
.w100{
width: 100px!important;
}
.w150{
width: 150px!important;
}
.w200{
width: 200px!important;
}
.w250{
width: 250px!important;
}
.w300{
width: 300px!important;
}
.w400{
width: 400px!important;
}
.w450{
width: 450px!important;
}
.ml120{
margin-left: 120px;
}
.fl{float: left;}
.fr{float: right}
.clearfix:after{
display: block;
clear: both;
content: '';
visibility: hidden;
height: 0;
}
.clearfix{
zoom: 1;
}
.cursor{
cursor: pointer;
}
.cursor-nodrop{
cursor: no-drop;
}
.blue{
color:$primary-color;
cursor: pointer;
}
.gray {
color: #909399;
}
.yellow {
color: #fede29;
}
.green{
color: green;
}
.gray-bg {
background-color: #f0f2f5;
}
.fz10{
font-size: 10px;
}
.fz11{
font-size: 11px;
}
.fz12{
font-size: 12px;
}
.fz13{
font-size: 13px;
}
.fz14{
font-size: 14px;
}
.fz16{
font-size: 16px;
}
.fz18{
font-size: 18px;
}
.fz20{
font-size: 20px;
}
.fz22{
font-size: 22px;
}
.fz24{
font-size: 24px;
}
.fz26{
font-size: 26px;
}
.fz28{
font-size: 28px;
}
.fz30{
font-size: 30px;
}
.fz40{
font-size: 40px;
}
.line-height2{
line-height:2;
}
.line-height1{
line-height:1;
}
.ellipsis-100{
@include ellipsis(100%);
}
.ellipsis-50{
@include ellipsis(50%);
}
.ellipsis-80{
@include ellipsis(80%);
}
.ellipsis{
max-height:20px;
line-height:20px;
overflow:hidden;
text-overflow:ellipsis;
display:-webkit-box;
-webkit-line-clamp:1;
-webkit-box-orient:vertical
}
.ellipsis-l2{
max-height:40px;
line-height:20px;
overflow:hidden;
text-overflow:ellipsis;
display:-webkit-box;
-webkit-line-clamp:2;
-webkit-box-orient:vertical
}
.ellipsis-l3{
max-height:60px;
line-height:20px;
overflow:hidden;
text-overflow:ellipsis;
display:-webkit-box;
-webkit-line-clamp:3;
-webkit-box-orient:vertical
}
.border1{
border: 1px solid #DCDFE6;
}
.border2{
border: 1px solid #E4E7ED;
}
.border3{
border: 1px solid #EBEEF5;
}
.border4{
border: 1px solid #F2F6FC;
}
.primary-color{
color: $primary-color;
}
.success-color{
color: $success-color;
}
.warning-color{
color: $warning-color;
}
.danger-color{
color: $danger-color;
}
.info-color{
color: $info-color;
}
.success-color-bg{
background-color: $success-color;
}
.warning-color-bg{
background-color: $warning-color;
}
.danger-color-bg{
background-color: $danger-color;
}
.info-color-bg{
background-color: $info-color;
}
.primary-font-color{
color: $primary-font-color;
}
.regular-font-color{
color: $regular-font-color;
}
.minor-font-color{
color: $minor-font-color;
}
.place-font-color{
color: $place-font-color;
}
.overflow-hidden{
overflow: hidden;
}
.inline-block{
display: inline-block;
}
.block{
display: block;
}
.inline{
display: inline;
}
.vertical-middle{
vertical-align: middle;
}
.vertical-top{
vertical-align: top;
}
.vertical-bottom{
vertical-align: bottom;
}
img {
position: relative;
}
img::after {
content: "";
height: 100%;
width: 100%;
position: absolute;
left: 0;
top: 0;
background-size: cover;
background-image: url(/marketing/static/img/failed-load_img.png);
// background-image: url(/static/img/failed-load_img.png);
}
.bold{
font-weight: bold;
}
.flex{
display: flex;
}
.border-radius__default{
border-radius: 4px;
}
/*隐藏滚轮*/
// ::-webkit-scrollbar {
// display: none;
// }
$primary-font-color: #303133;
$regular-font-color: #606266;
$minor-font-color: #909399;
$place-font-color: #C0C4CC;
$primary-color: #409EFF;
$search-width: 180px;
$border-color:#DCDFE6;
$border-color2:#E4E7ED;
$border-color3:#EBEEF5;
$border-color4:#F2F6FC;
$gray-color:#f4f5f9;
$success-color:#67C23A;
$warning-color:#E6A23C;
$danger-color:#F56C6C;
$info-color:#909399;
@import '../base/var.scss';
.dm-select {
width: 130px!important;
}
.dm-tabs .el-tabs__header{
padding: 0 30px;
background: #fff;
}
.dm-pagination {
text-align: right;
margin: 24px 0 10px 0;
}
.label-hidden .el-checkbox__label,.label-hidden .el-radio__label{
opacity: 0;
position: absolute;
}
.dm-upload,.dm-upload_right{
position: relative;
}
.dm-upload .el-upload-list {
position: absolute;
top: 26px;
left: -26px;
}
.dm-upload_right .el-upload-list {
position: absolute;
top: 0px;
right: 20px;
}
.el-select{
width: 100%;
}
.dm-input_label{
font-size: 14px;
color: #333;
display: inline-block;
}
.dm-label-input_80{
width: calc(100% - 80px);
}
.dm-label-input_100{
width: calc(100% - 10-px);
}
.el-table .cell {
font-size: 14px;
}
#app .el-tabs__nav{
z-index: 1;
}
#app .el-breadcrumb__inner {
color: #303133;
}
#app .user-header-pop {
min-width: 95px;
}
#app .el-popover.user-header-pop {
min-width: 95px;
}
.el-select-dropdown__wrap {
max-height: 310px!important;
}
.dm-tabs-wrap{
position: relative;
top: -1px;
.el-tabs__header {
background: #fff;
}
.el-tabs__nav{
text-align: center;
}
.el-tabs__item{
text-align: center;
padding: 0 20px!important;
margin: 0 auto;
&.active{
border-bottom: 2px solid $primary-color;
}
}
}
.dm-tabs /deep/ .el-tabs__content {
margin: 0 30px;
background: #fff;
}
.dm-tabs /deep/ .el-tabs__content>div{
margin: 20px;
}
.el-table__empty-text{
width: auto;
margin-bottom:80px;
&::before{
content: ' ';
display: block;
width: 60px;
height: 60px;
background: url(/marketing/static/img/no-data_icon.png) no-repeat center;
margin: 80px auto 22px auto;
}
}
.btn-wrap_fixed{
position: fixed;
bottom: 0px;
// left: 0;
right: 0;
width: 100%;
text-align: center;
background: #fff;
height: 57px;
line-height: 57px;
border-top: 1px solid #E4E7ED;
z-index: 999;
&.on{
width: calc(100% - 200px);
left: 200px;
}
.el-button{
padding: 9px 15px;
font-size: 12px;
border-radius: 3px;
}
.el-button+.el-button {
margin-left: 7px;
}
}
.dm-tabs-wrap{
.el-tabs__header {
margin: 0;
}
}
.el-select,.el-input,.el-checkbox.is-bordered,.el-date-editor{
margin-right: 5px;
}
.el-form-item__margin-top14 {
.el-form-item__label{
margin-top:14px;
}
}
@mixin ellipsis($width){
text-overflow: ellipsis;
overflow: hidden;
max-width: $width;
white-space: nowrap;
word-wrap: normal;
width: auto;
}
<template>
<el-dialog title="修改数量" :visible.sync="show" width="30%" :before-close="close" append-to-body>
奖品数量 <el-radio v-model="calculate" :label="true">增加</el-radio>
<el-radio v-model="calculate" :label="false">减少</el-radio>
<div class="text-center pt10">
<el-input-number :min="0" v-model="count" class="w200 block-center" @blur="checkCalc"></el-input-number>
<span class="fz14 pl20">结果:</span>
<span>{{calculate?parseInt(totalCount)+parseInt(count):parseInt(totalCount)-parseInt(count)}}</span>
</div>
<div class="text-center pt10">
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="close">取 消</el-button>
<el-button type="primary" @click="submit">确 定</el-button>
</span>
</el-dialog>
</template>
<script>
import {updatePrizeStock} from '@/service/api/gameApi.js';
export default {
props:{
totalCount:{
type:Number,
default:0
},
show:{
type:Boolean,
default:false
},
prizeReferType:{
type:Number,
default:0
},
prizeId:{
type:String,
default:''
},
prizeReferId:{
type:String,
default:''
},
useStrategy:{
type:Number,
default:1
},
},
data(){
return{
count:0,
calculate:true
}
},
methods:{
checkCalc(){
if (!this.calculate) {
if (this.count>this.totalCount) {
this.count = this.totalCount;
this.$tips({type:'warning',message:'输入数量不能大于总数'})
}
}
},
close(){
this.$emit('update:show',false)
// this.count = 0;
},
submit(){
if (!this.calculate) {
if (this.count>this.totalCount) {
this.count = this.totalCount;
this.$tips({type:'warning',message:'输入数量不能大于总数'})
return;
}
}
let params = {
optType:this.calculate?1:2, //是 int 1: 增加; 2: 减少
addCount:this.count, //是 int 增加或者减少数量
prizeReferType:this.prizeReferType, // 是 int 奖品类型: 积分: 1; 卡券: 2; 礼品: 3
useStrategy:this.useStrategy,//是 int 首奖特殊策略: 1; 奖项设置: 0
prizeReferId:this.prizeReferType===1?null:this.prizeReferId, //是 string 卡券, 礼品都存在, 积分传null
prizeId:this.prizeId,
}
updatePrizeStock(params).then(res => {
this.$emit('countRefresh',{calc:this.calculate,num:this.count});
});
this.close();
}
}
}
</script>
<template>
<section class="c-p">
<div class="c-p_l">
<header>
<el-input style="width:200px;" placeholder="请输入昵称卡号" prefix-icon="el-icon-search" v-model="keyWord" clearable @change="loadList"></el-input>
</header>
<el-table v-loading="loading" tooltipEffect="light" ref="multipleTable" :data="leftList" tooltip-effect="dark" style="width: 100%" height="400px" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55"></el-table-column>
<el-table-column label="会员信息" min-width="120">
<template slot-scope="scope">
<img style="vertical-align: middle;" :src="scope.row.photoUrl || avatar" width="32px" height="32px" alt="" srcset="">
<span class="avatar">
<p>{{ scope.row.name }}</p>
<p style="color:#909399;">{{ scope.row.cardNo }}</p>
</span>
</template>
</el-table-column>
<el-table-column prop="gradeName" label="会员等级" width="100"></el-table-column>
</el-table>
</div>
<div class="c-p_c">
<el-button type="primary" icon="el-icon-arrow-right" circle @click="toRight"></el-button>
<el-button icon="el-icon-arrow-left" circle @click="removeAll"></el-button>
</div>
<div class="c-p_r">
<header>
<el-input style="width:200px;" placeholder="请输入昵称卡号" clearable prefix-icon="el-icon-search" v-model="keyWordRight" @change="filterRight"></el-input>
</header>
<el-table tooltipEffect="light" ref="multipleTable" :data="rightList" tooltip-effect="dark" style="width: 100%" height="400px" @selection-change="handleSelectionChangeRight">
<el-table-column type="selection" width="55"></el-table-column>
<el-table-column label="会员信息" min-width="120">
<template slot-scope="scope">
<img style="vertical-align: middle;" :src="scope.row.photoUrl || avatar" width="32px" height="32px" alt="" srcset="">
<span class="avatar">
<p>{{ scope.row.name }}</p>
<p style="color:#909399;">{{ scope.row.cardNo }}</p>
</span>
</template>
</el-table-column>
<el-table-column prop="gradeName" label="会员等级" width="100"></el-table-column>
<el-table-column prop="gradeName" label="操作" width="50">
<template slot-scope="scope">
<el-button type="text" @click="remove(scope.$index)">移除</el-button>
</template>
</el-table-column>
</el-table>
</div>
</section>
</template>
<script>
import qs from 'qs'
/**
* 清除重复
*/
function uniqueByType(arr, type) {
var res = [];
var json = {};
for (var i = 0; i < arr.length; i++) {
if (!json[arr[i][type]]) {
res.push(arr[i]);
json[arr[i][type]] = 1;
}
}
return res;
}
let allList = [];
export default {
props:{
idType:{
type:String,
default:'openId'
},
list:{
type:Array,
default() {
return []
}
},
},
data() {
return {
loading:false,
keyWord:'',
keyWordRight:'',
leftList:[],
rightList:[],
leftSelected:[],
rightSelected:[],
avatar:require('../../assets/img/head_default.jpg')
}
},
watch: {
rightList(val) {
// console.log(val)
this.$emit('get-data',val.map(v => v[this.idType]).join(','));
},
list(val) {
allList = this.rightList = val.slice();
}
},
methods:{
handleSelectionChange(val) {
// console.log(val)
this.leftSelected = val;
},
handleSelectionChangeRight(val){
this.rightSelected = val;
},
toRight(){
// console.log(this.leftSelected)
this.leftSelected.map( v => {
const index = this.rightList.indexOf(v);
if (index < 0) {
this.rightList.push(v);
}
})
console.log(this.rightList.map(v => v.memberId));
allList = this.rightList = uniqueByType(this.rightList,'memberId')
},
loadList() {
this.loading = true;
this.leftSelected = []
let params = {
searchParams:this.keyWord,
onlyWxMember:1,
requestProject:'gic-web'
}
this.axios.post('/api-marketing/member-search-engine',qs.stringify(params),
{})
.then(res => {
if (res.data.errorCode === 0 && res.data.result) {
this.leftList = res.data.result;
}
this.loading = false;
})
},
removeAll() {
let currentList = this.rightList.slice();
this.rightSelected.map(v => {
const index = currentList.indexOf(v);
if (index > -1) {
currentList.splice(index,1)
}
})
allList = this.rightList = currentList;
},
remove(index) {
this.rightList.splice(index,1);
allList.splice(index,1);
},
//TODO
filterRight() {
if (!this.keyWordRight) {
this.rightList = Object.assign([],allList);
} else {
const list = Object.assign([],this.rightList);
const filterList = list.filter(v => {
let flag = false;
if(v.cardNo) flag = v.cardNo.indexOf(this.keyWordRight) > -1;
if(v.memberName) flag = v.memberName.indexOf(this.keyWordRight) > -1;
return flag;
});
this.rightList = filterList;
}
}
}
}
</script>
<style lang="scss" scoped>
.c-p{
width: 1000px;
&_l,&_r{
display: inline-block;
width: 400px;
border: 1px solid #DCDFE6;
vertical-align: middle;
header{
height: 80px;
line-height: 80px;
padding: 0 10px;
}
}
&_c {
display: inline-block;
vertical-align: middle;
margin: 0 10px;
button{
display: block;
margin: 10px auto;
}
}
/deep/ .avatar{
display: inline-block;vertical-align: middle;
}
}
</style>
<template>
<el-option :value="value" :label="label" :disabled="disabled">
<span style="float: left">{{ item.value }}</span>
<span style="float: right; color: #8492a6; font-size: 13px">{{ item.label }}</span>
</el-option>
</template>
<script>
export default {
props: {
value: {
required: true
},
label: [String, Number],
disabled: {
type: Boolean,
default: false
}
},
computed: {
parent() {
let parent = this.$parent;
let parentName = parent.$options.componentName;
while (parentName !== 'BDSelect') {
parent = parent.$parent;
parentName = parent.$options.componentName;
}
return parent;
}
},
created() {
this.parent.options.push({ label: this.label, value: this.value });
}
}
</script>
import {elTable} from 'element-ui';
import Vue from 'vue';
const CompVue = Vue.extend(elTable)
export default = new CompVue({
})
<template>
<el-input v-model="input" placeholder="请输入内容"></el-input>
</template>
x<template>
<div class="stock-input">
<bd-select
ref="bd-select"
v-model="selectConf.value"
:disabled="disabled"
multiple
:loading="selectConf.loading"
filterable
remote
:remote-method="remoteMethod"
:placeholder="placeholder"
@change="selectChange">
<el-option
v-for="option in selectConf.options"
:key="option.value"
:value="option.value"
:label="option.label">
<div class="suggest-item">
<!-- <span class="suggest-item__rt">{{option.value}}</span> -->
<span class="suggest-item__lt">{{option.label}} </span>
</div>
</el-option>
</bd-select>
</div>
</template>
<script>
import emitter from 'element-ui/lib/mixins/emitter';
import dmSelect from '@/components/dm-select';
import {loadGoodsData} from '@/service/api/cardApi.js'
// import InduPicker from '@components/Customer/common/com_induPicker';
export default {
props: {
value: Array,
disabled: Boolean,
userId: String,
multiple: {
type: Boolean,
default: true
},
placeholder: {
type: String,
default: '输入货号或商品名称'
},
withparent: {
type: Boolean,
default: true
},
beforeSelect: Function
},
data() {
return {
selectConf: {
value: !!this.value.find(p => !!p.id) ? this.value.map(item => item.id) : [],
options: this.value.map(item => {
return { value: item.id, label: item.name };
}),
loading: false
},
// pickerConf: {
// show: false,
// selected: []
// },
timer: null,
cacheIndus: this.value.map(item => item)
};
},
mixins: [emitter],
watch: {
value: {
handler: function (val) {
this.cacheIndus = val.map(item => item);
this.selectConf.value = val.map(item => item.id);
this.selectConf.options = val.map(item => {
return { value: item.id, label: item.name };
});
},
deep: true
}
},
components: {
'bd-select': dmSelect
},
methods: {
remoteMethod(val) {
// if (!val) {
// this.$tips({type:'warning',message:'搜索字段不能为空'});
// return;
// }
if (!val && this.selectConf.options.length > 0) return;
this.selectConf.loading = true;
clearTimeout(this.timer);
this.timer = setTimeout(() => {
loadGoodsData({currentPage:1,pageSize:20,searchParam:val}).then(res => {
this.selectConf.options = [];
res = res.result.result || [];
this.$nextTick(() => {
if (!!res && res instanceof Array) {
res.forEach(item => {
this.selectConf.options.push({
label: item.proName,
value: item.proNo
});
if (this.cacheIndus.findIndex(stk => stk.id === item.label) === -1) {
this.cacheIndus.push({
id: item.proNo,
name: item.proName
});
}
});
}
this.selectConf.loading = false;
});
});
}, 300);
},
selectChange(val) {
if (!this.multiple && val.length > 0) {
this.selectConf.value.splice(0, this.selectConf.value.length - 1);
val.splice(0, val.length - 1);
this.$nextTick(_ => {
this.$refs['bd-select'].$refs.select.visible = false;
})
}
let arr = this.cacheIndus.filter(item => val.findIndex(v => v === item.id) !== -1);
let arrCopy = JSON.parse(JSON.stringify(arr));
if (!!this.beforeSelect && this.beforeSelect instanceof Function && !this.beforeSelect(arrCopy)) return;
this.$emit('input', arr);
this.$emit('change', arr);
},
iconClick() {
this.remoteMethod();
},
induSelected(items) {
this.selectConf.value = [];
if (items.length === 0) return;
items.forEach(item => {
if (this.selectConf.value.findIndex(v => v === item.id) === -1) {
this.selectConf.value.push(item.id);
}
if (this.selectConf.options.findIndex(option => option.value === item.id) === -1) {
this.selectConf.options.push({
label: item.label,
value: item.id,
code: item.code
});
}
if (this.cacheIndus.findIndex(cache => cache.id === item.id) === -1) {
this.cacheIndus.push({
id: item.id,
name: item.label,
code: item.code
});
}
});
this.selectChange(this.selectConf.value);
this.dispatch('ElFormItem', 'el.form.change', this.selectConf.value);
}
}
}
</script>
<style lang="scss" scoped>
.suggest-item {
font-size: 12px;
height: 24px;
line-height: 24px;
&__lt {
float: left;
width: calc(100% - 34px);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
&__rt {
float: right;
color: #bbb;
width: 30px;
// padding-right: 10px;
text-align: right;
}
}
</style>
import lgPreview from './preview.vue'
export default {
install: function (Vue, options) {
// 添加的内容写在这个函数里面
const LOGIC_EVENT_BUS = new Vue({
data(){
return {
LOGIC_PREVIEW : {
isTitleEnable: true,
isHorizontalNavEnable: true,
show: false,
loading : true,
current: {
title: '',
src: ''
},
list: []
}
}
}
})
window.LOGIC_EVENT_BUS = LOGIC_EVENT_BUS
Vue.component('lg-preview', lgPreview)
const updateIndex = function (list) {
list.forEach(function (item, index) {
item.index = index + 1
})
}
function getImage (src, previewItem) {
return new Promise(function (resolve, reject) {
const img = new window.Image()
img.src = src
img.onload = function () {
previewItem['naturalHeight'] = img.naturalHeight
previewItem['naturalWidth'] = img.naturalWidth
setTimeout(function () {
LOGIC_EVENT_BUS.LOGIC_PREVIEW.loading = false
},500)
resolve(img)
}
img.error = function (e) {
reject(e)
}
})
}
Vue.directive('preview', {
bind: function (el) {
var previewItem = {
title: '',
el: el,
index: 0,
src: ''
}
LOGIC_EVENT_BUS.LOGIC_PREVIEW.list.push(previewItem)
updateIndex(LOGIC_EVENT_BUS.LOGIC_PREVIEW.list)
el.addEventListener('click', function (e) {
e.stopPropagation()
LOGIC_EVENT_BUS.LOGIC_PREVIEW.isTitleEnable = el.getAttribute('preview-title-enable')== "false" ? false : true;
LOGIC_EVENT_BUS.LOGIC_PREVIEW.isHorizontalNavEnable = el.getAttribute('preview-nav-enable')== "false" ? false : true;
LOGIC_EVENT_BUS.LOGIC_PREVIEW.show = true
LOGIC_EVENT_BUS.LOGIC_PREVIEW.loading = true
LOGIC_EVENT_BUS.LOGIC_PREVIEW.current = previewItem
getImage(previewItem.src, previewItem)
})
},
update: function (el, oldValue) {
var previewItem = LOGIC_EVENT_BUS.LOGIC_PREVIEW.list.find(function (item) {
return item.el === el
})
if (!previewItem) return
previewItem.src = oldValue.value
previewItem.title = el.alt
},
unbind: function (el) {
if (el) {
LOGIC_EVENT_BUS.LOGIC_PREVIEW.list.forEach(function (item, index) {
if (el === item.el) {
LOGIC_EVENT_BUS.LOGIC_PREVIEW.list.splice(index, 1)
}
})
}
updateIndex(LOGIC_EVENT_BUS.LOGIC_PREVIEW.list)
}
})
}
};
<template>
<transition name="fade">
<div class="lg-preview-wrapper" v-show="preview.show" @click="leave" @touchmove.prevent>
<div class="lg-preview-loading" v-show="preview.loading"><div></div></div>
<img
class="lg-preview-img"
v-if="preview.current.src"
:src="preview.current.src"
:alt="preview.current.title"
v-show="!preview.loading"
>
<div class="lg-preview-title" v-if="preview.isTitleEnable&&preview.current.title" v-show="!preview.loading">
{{preview.current.title}}
</div>
<div class="lg-preview-nav-left" v-if="preview.isHorizontalNavEnable" v-show="!preview.loading">
<span class="lg-preview-nav-arrow" @click="preAction" ></span>
</div>
<div class="lg-preview-nav-right" v-if="preview.isHorizontalNavEnable" v-show="!preview.loading">
<span class="lg-preview-nav-arrow" @click="nextAction"></span>
</div>
</div>
</transition>
</template>
<script>
export default {
name: 'Preview',
computed: {
preview () {
return window.LOGIC_EVENT_BUS.LOGIC_PREVIEW
}
},
methods: {
leave (e) {
if ((this.preview.show)&&(e.target.className.indexOf('lg-preview-nav-arrow') != 0)){
this.close()
}
},
close () {
this.preview.show = false
},
preAction () {
this.preview.loading = true
var index = this.preview.list.indexOf(this.preview.current)
if (index === 0) {
this.preview.loading = false
return
}
index--
this.preview.current = this.preview.list[index]
const img = new window.Image()
img.src = this.preview.current.src
img.onload = function () {
setTimeout(function () {
LOGIC_EVENT_BUS.LOGIC_PREVIEW.loading = false
},500)
}
},
nextAction () {
this.preview.loading = true
var index = this.preview.list.indexOf(this.preview.current)
if (index === this.preview.list.length - 1) {
this.preview.loading = false
return
}
index++
this.preview.current = this.preview.list[index]
const img = new window.Image()
img.src = this.preview.current.src
img.onload = function () {
setTimeout(function () {
LOGIC_EVENT_BUS.LOGIC_PREVIEW.loading = false
},500)
}
},
}
}
</script>
<style scoped>
.fade-enter-active, .fade-leave-active {
transition: opacity .5s
}
.fade-enter, .fade-leave-active {
opacity: 0
}
.lg-preview-wrapper {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
text-align: center;
box-sizing: border-box;
background: rgba(0, 0, 0, 0.9);
z-index: 10000;
}
.lg-preview-loading {
position: absolute;
width: 30px;
height: 35px;
top: 50%;
left: 50%;
margin-top: -17.5px;
margin-left: -15px;
}
.lg-preview-loading > div {
display: inline-block;
height: 25px;
width: 25px;
background: transparent;
border-radius: 100%;
border: 2px solid #fff;
border-bottom-color: transparent;
-webkit-animation-fill-mode: both;
animation-fill-mode: both;
-webkit-animation: rotate 0.75s 0s linear infinite;
animation: rotate 0.75s 0s linear infinite;
}
@keyframes rotate {
0% {
-webkit-transform: rotate(0deg) scale(1);
transform: rotate(0deg) scale(1);
}
50% {
-webkit-transform: rotate(180deg) scale(0.6);
transform: rotate(180deg) scale(0.6);
}
100% {
-webkit-transform: rotate(360deg) scale(1);
transform: rotate(360deg) scale(1);
}
}
.lg-preview-img {
max-width: 100%;
max-height: 100%;
display: block;
position: absolute;
left: 0;
top: 0;
bottom: 0;
right: 0;
margin: auto;
}
.lg-preview-nav-arrow {
position: absolute;
top: 50%;
margin-top: -15px;
background: rgba(0, 0, 0, 0);
line-height: 40px;
width: 20px;
height: 20px;
border-top: 2px solid #fff;
border-left: 2px solid #fff;
}
.lg-preview-nav-left,
.lg-preview-nav-right {
position: absolute;
height: 100%;
margin: 0 5px;
width: 200px;
top: 0;
color: #fff;
transition: opacity .2s;
}
.lg-preview-nav-left {
left: 0;
}
.lg-preview-nav-left .lg-preview-nav-arrow {
left: 0;
margin-left: 40px;
transform: rotate(-45deg);
}
.lg-preview-nav-right {
right: 0;
}
.lg-preview-nav-right .lg-preview-nav-arrow {
right: 0;
margin-right: 40px;
transform: rotate(135deg);
}
.lg-preview-title {
position: absolute;
left: 0;
bottom: 0;
text-align: center;
width: 100%;
color: #fff;
background: rgba(0, 0, 0, .5);
box-sizing: border-box;
font-size: 16px;
height: 40px;
line-height: 40px;
}
@media all and (max-width: 768px) {
.lg-preview-nav-left,
.lg-preview-nav-right {
width: 100px;
}
.lg-preview-nav-left .lg-preview-nav-arrow {
margin-left: 20px;
}
.lg-preview-nav-right .lg-preview-nav-arrow {
margin-right: 20px;
}
}
</style>
import upload from './upload';
export default {
upload
}
<template>
<div class="innerUrl-wrap menu-item">
<div class="menu-item">
<el-select @change="selectLeftUrl" class="fl select-shop" label="黄精" v-model="leftUrl" placeholder="请选择">
<el-option
v-for="item in leftOptions"
:key="item.wechat_menu_dict_id"
:label="item.wechat_menu_dict_name"
:value="item.wechat_menu_dict_id">
</el-option>
</el-select>
<div v-show="leftUrl=='12'" class="fr select-shop">
<el-input @focus="dialogShopShow" suffix-icon="el-icon-arrow-down" v-model="selectShopUrl" placeholder="选择门店"></el-input>
</div>
<el-select v-show="leftUrl!='12'&&rightOptions.length>0 || leftUrl!='12'&&rightUrl!=''" @change="selectRighgtUrl" class="fr select-shop" label="黄精" v-model="rightUrl" placeholder="请选择">
<el-option
v-for="item in rightOptions"
:key="item.wechat_menu_dict_id"
:label="item.wechat_menu_dict_name"
:value="item.wechat_menu_dict_url">
</el-option>
</el-select>
</div>
<!-- 选择门店 -->
<div class="choose-wrap">
<vue-gic-store :showStoreDialog="showStoreDialog" @selectStore="selectStore"></vue-gic-store>
</div>
</div>
</template>
<script>
let qs = require('qs')
export default {
name: "innerUrl",
props:{
menuCode:String
},
data() {
return {
/*内部链接 级联选择器*/
leftOptions:[],
rightOptions:[],
leftUrl:'',
rightUrl:'',
dialogShop: false, // 选择门店的弹窗
selectShopUrl: '', // 门店的弹窗,选中之后的url
tableData2: [], // 门店列表
searchShopValue: '', // 搜索门店
radioShop:null,
showShopDetail: '', // 用于显示选中的门店
totalCount:null,
currentPage:1, // 分页
pageSize:5,
edit:true,
defaultUrl:false,
showStoreDialog: false, // 门店
baseUrl:'',
}
},
watch: {
menuCode(newVal,oldVal){ // 内链
console.log(newVal);
if(newVal=="null" || newVal=="" || newVal==undefined){
return;
}else{
var valObj = JSON.parse(newVal);
console.log(valObj)
this.leftUrl = valObj.precode;
this.rightUrl = valObj.nextcode;
this.defaultUrl = true;
this.selectLeftUrl(this.leftUrl,this.rightUrl); // 获取默认信息
}
}
},
created(){
// console.log(this.$parent.menuCode);
},
mounted(){
this.getLeftUrlData(); // 获取左侧地址
// this.getShopList(); // 获取门店列表
},
methods:{
getLeftUrlData(){ // 获取左侧
this.axios.post('/api-admin/get-inner-url',qs.stringify({
requestProject:'gic-web'
})).then((res)=>{
var data = res.data;
// console.log(data);
if(data.errorCode==0){
this.leftOptions = data.result;
}else{
this.$message({
type:'error',
message:data.message
})
}
})
},
selectLeftUrl(val,valR){ // 选择左侧链接 获取右侧
this.leftUrl = val;
this.rightUrl = valR;
//console.log(this.leftUrl);
//console.log(this.rightUrl);
// if (leftUrl)
// let leftVal = '';
// this.leftOptions.map(v => {
// if (v.wechat_menu_dict_id == this.leftUrl) {
// leftVal = v.wechat_menu_dict_url
// }
// })
// // debugger
// console.log(this.leftOptions.map(v => v.wechat_menu_dict_url),leftVal)
// this.$emit('sendLeftUrl',this.leftUrl);
// this.$emit('sendRightUrl',this.rightUrl);
if(!this.defaultUrl){
this.rightUrl = '';
}
this.axios.post('/api-admin/pre-code-select',qs.stringify({
requestProject:'gic-web',
pid:val
})).then((res)=>{
var data = res.data;
// console.log(data);
if(data.errorCode==0){
this.rightOptions = data.result.menuList;
if (this.rightOptions[0].wechat_menu_dict_id === '112' || this.rightOptions[0].wechat_menu_dict_name === '选择门店') {
this.baseUrl = this.rightOptions[0].wechat_menu_dict_url;
// debugger
} else {
this.baseUrl = ''
}
this.defaultUrl = false
}else{
this.$message({
type:'error',
message:data.message
})
}
})
},
selectRighgtUrl(val){ // 选择右侧链接
this.rightUrl = val;
console.log(this.rightUrl);
let name = '';
this.rightOptions.map(v => {
if (v.wechat_menu_dict_url === val) {
name = v.wechat_menu_dict_name
}
})
this.$emit('sendRightUrl',{name:name,url:val});
},
dialogShopShow(){ // 显示门店弹窗
var that = this;
that.showStoreDialog = true;
},
selectStore(val) { // 子组件触发方法
// 模拟检查数据
console.log(val);
this.showStoreDialog = false;
this.selectShopUrl = val.storeName;
console.log(this.selectShopUrl);
this.$emit('sendShopData',{name:val.storeName,url:this.baseUrl + '?id='+val.storeId});
},
// getShopList(){ // 获取门店列表
// this.axios.post('/api-admin/store-list-paging',qs.stringify({
// searchName:'',
// completeStatus:1,
// status:1,
// channelType:0,
// storeGroupId:'',
// pageSize:this.pageSize,
// currentPage:this.currentPage
// })).then((res)=>{
// var data = res.data;
// // console.log(data);
// if(data.errorCode==0){
// var result = data.result;
// this.tableData2 = result.list;
// this.totalCount = result.page.totalCount
// }else{
// this.$message({
// type:'error',
// message:data.message
// })
// }
// })
// },
// handleCurrentChangeSelectShop(val){ // 选择门店发生变化时触发
// this.radioShop = val.storeId;
// this.showShopDetail = val.storeName
// },
// chooseShopBtn(){ // 选择门店
// if(this.radioShop==''){
// this.dialogShop = false;
// } else {
// this.dialogShop = false;
// this.selectShopUrl = this.showShopDetail
// }
// },
// handleSizeChange(val){ // 选择分页符
// console.log(`每页 ${val} 条`);
// },
// handleCurrentChange(val) {
// console.log(`当前页: ${val}`);
// },
}
}
</script>
<style scoped lang="scss">
.innerUrl-wrap{
.menu-item{
width: 450px;
position: relative;
.select-shop{
width: 220px;
}
}
.choose-wrap{
.search{
width: 260px;
margin-bottom: 24px;
}
.choose-list{
max-height: 404px;
border-top: 1px solid #ebeef5;
.list-img{
width: 80px;
height: 80px;
img{
width: 100%;
height: 100%;
}
}
}
.pagination{
text-align: right;
margin-top: 20px;
line-height: 1
}
}
}
.choose-shop-list /deep/ .el-table__row{
cursor: pointer;
}
.choose-shop-list /deep/ .el-table .has-gutter{
line-height: 1
}
.choose-wrap /deep/ .el-dialog__body{
padding: 10px 20px;
}
.choose-wrap /deep/ .el-dialog {
height: auto!important;
}
</style>
<template>
<div class="layout-container">
<vue-gic-header class="user-header-pop" style="z-index: 1999;" :projectName="projectName" :collapseFlag="collapseFlag" @collapseTag="collapseTagHandler" @toRouterView="toRouterView"></vue-gic-header>
<div class="layout">
<vue-gic-aside-menu v-if="asideShow" :projectName="projectName" :leftModulesName="leftModulesName" :collapseFlag.sync="collapseFlag"></vue-gic-aside-menu>
<div class="layout-right">
<div class="layout-title">
<el-breadcrumb class="dm-breadcrumb" separator="/">
<el-breadcrumb-item :to="{ path: '' }"><a href="/report/#/memberSummary">首页</a></el-breadcrumb-item>
<el-breadcrumb-item :class="{'no-link':!v.path}" v-for="(v,i) in breadcrumb" :key="i" :to="{ path: v.path }">{{v.name}}</el-breadcrumb-item>
</el-breadcrumb>
<h3><span>{{contentTitle}}</span></h3>
</div>
<div class="layout-content">
<router-view></router-view>
</div>
<vue-gic-footer></vue-gic-footer>
</div>
</div>
</div>
</template>
<script>
export default {
data () {
return {
collapseFlag: false,
projectName: "marketing",
leftModulesName: '公众号配置',
}
},
created(){
$bus.$on('aside-menu',val => {
this.leftMenuRouter = val
})
},
destroyed() {
$bus.$off('aside-menu');
},
computed: {
asideShow() {
console.log(this.$store.state)
return this.$store.state.marketing.asideShow;
},
contentTitle() {
return this.$route.name
},
breadcrumb() {
return this.$store.state.marketing.breadcrumb;
}
},
methods: {
// 处理路由跳转
toRouterView(val) {
var that = this;
// 模拟检查数据
// //有两个参数
//{
// name:,
// path:
//}
that.$router.push({
path: val
})
},
// 处理路由跳转
toRouterView(val) {
//有两个参数
//{
// name:,
// path:
//}
this.$router.push({
path: val.path
})
},
// 折叠事件
collapseTagHandler(val){
this.collapseFlag = val
}
}
}
</script>
<style lang="scss">
.layout-container{
height:100%;
display:flex;
}
.layout {
display: flex;
flex-direction: row;
background-color: #f0f2f5;
height: calc(100% - 64px);
overflow-x: auto;
overflow-y: hidden;
margin-top: 64px;
width: 100%;
&-right{
flex: 1;
overflow-x:auto;
transition: width 0.5s;
-moz-transition: width 0.5s;
-webkit-transition: width 0.5s;
-o-transition: width 0.5s;
height: 100%;
width: 1200px;
overflow-y: auto;
}
&-title{
height:85px;
background:#fff;
// box-shadow: 0 3px 5px rgba(147,165,184,.13);
padding:15px 0 0 30px;
border-bottom: 1px solid #e4e7ed;
h3 {
color:#303133;
font-size:20px;
padding:24px 0;
font-weight:600;
span{
color:#303133;
font-size:20px;
font-weight:600;
}
i{
font-size:20px;
color:#c0c4ce;
cursor: pointer;
&:hover{
color:#909399;
}
}
}
}
&-content {
min-height: calc(100% - 200px);
}
}
.dm-breadcrumb{
display: inline-block;
vertical-align: middle;
}
.user-header-pop {
min-width: 95px;
}
.el-popover.user-header-pop {
min-width: 95px;
}
</style>
<template>
<el-dialog title="语音管理" :visible.sync="show" width="40%" :before-close="close" v-loading="loading">
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
<el-form-item label="标题" prop="titleName">
<dm-input v-model="form.titleName" :maxlength="64"></dm-input>
</el-form-item>
<el-form-item label="分类">
<el-select v-model="form.voiceCategory">
<el-option v-for="item in dictList"
:key="item.dict_id"
:label="item.dict_name"
:value="item.dict_id"></el-option>
</el-select>
</el-form-item>
<el-form-item label="语音内容" v-if="!this.voiceId">
<el-upload
class="dm-upload"
:action="api+'/api-marketing/upload-marketing-audiosave'"
:on-preview="handlePreview"
:data="{requestProject:'marketing'}"
:on-remove="handleRemove"
:before-remove="beforeRemove"
enctype="multipart/form-data"
:on-success="uploadSuccess"
:auto-upload="true"
:multiple="false"
:limit="1"
:on-exceed="handleExceed"
:file-list="fileList"
>
<el-button size="small" type="primary">上传语音</el-button>
</el-upload>
<p class="gray fz12">格式支持mp3、wma、wav、amr,文件大小不超过30M,语音时长不超过30分钟</p>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="close">取 消</el-button>
<el-button type="primary" @click="submit('form')">确 定</el-button>
</span>
</el-dialog>
</template>
<script>
import config from '@/config';
import { editAudioDetailService ,uploadAudioService,saveAudioService} from "@/service/api/wechatApi.js";
export default {
props:{
show:{
type:Boolean,
default:false
},
voiceId:String
},
watch: {
show(val) {
if (val) {
this.editAudioDetailService({voiceId:this.voiceId});
}
}
},
data() {
return {
api:config.api,
loading:false,
radio:'',
dictList:[],
form:{titleName:'',voiceCategory:''},
fileList: [],
rules:{
titleName:[
{required:true, message: '请输入文件名', trigger: 'change' }]
},
voiceMediaId:''
};
},
methods: {
close() {
this.$emit('update:show',false);
this.voiceMediaId = ''
},
submit(formName) {
if (!this.voiceId && !this.voiceMediaId) {
this.$tips({type:'warning',message:'请上传语音文件再提交'});
return;
}
this.$refs[formName].validate((valid) => {
if (valid) {
this.saveAudioService();
} else {
console.log('error submit!!');
return false;
}
});
},
async editAudioDetailService(val) {
try {
this.loading = true;
let res = await editAudioDetailService(val);
this.dictList = res.result.gicDictDtoList;
this.form.titleName = res.result.wechatVoiceDTO ? res.result.wechatVoiceDTO.titleName : '';
this.form.voiceCategory = res.result.wechatVoiceDTO ? res.result.wechatVoiceDTO.voiceCategory :'';
this.voiceMediaId = res.result.wechatVoiceDTO ? res.result.wechatVoiceDTO.voiceMediaId : '';
this.loading = false;
} catch (err) {
console.log(err)
this.$tips({type:'error',message:'初始化失败,请稍后再试'});
this.loading = false;
}
},
//保存操作
async saveAudioService() {
try {
this.loading = true;
let params = {voiceId:this.voiceId,titleName:this.form.titleName,voiceCategory:this.form.voiceCategory,voiceMediaId:this.voiceMediaId}
let res = await saveAudioService(params);
this.loading = false;
this.$tips({type:'success',message:'保存语音成功'});
this.$emit('update:show',false);
this.$emit('refresh');
console.log(res);
} catch (err) {
this.$tips({type:'error',message:'保存语音失败'});
}
},
//上传相关
handleRemove(file, fileList) {
console.log(file, fileList);
},
handlePreview(file) {
console.log(file);
},
handleExceed(files, fileList) {
this.$message.warning(`只能上传一个文件,请先移除文件`);
},
beforeRemove(file, fileList) {
return this.$confirm(`确定移除 ${ file.name }?`);
},
uploadSuccess(response,file,fileList) {
if (response.errorCode === 0) {
this.$tips({type:'success',message:'上传成功'});
this.voiceMediaId = response.result;
} else {
this.$tips({type:'error',message:response.message});
}
},
}
};
</script>
<template>
<li class="audio-item">
<div class="audio-item_top clearfix">
<img class="fl" src="../../../assets/img/icon_audio.gif" alt="" srcset="">
<div class="fl">
<h5 class="ellipsis-100">{{audioData.titleName || '未命名语音'}}</h5>
<p class="gray fz12 ellipsis-100">创建于:{{audioData.createTimeStr}}</p>
</div>
</div>
<div class="audio-item_bottom">
<i class="el-icon-edit" @click="edit"></i>
<i class="el-icon-delete" @click="del"></i>
</div>
</li>
</template>
<script>
export default {
props: {
audioData: {
type: Object,
default: function () {
return {
}
}
}
},
methods: {
edit(){
this.$emit('edit',this.audioData.voiceId);
},
del() {
this.$emit('del',{voiceId:this.audioData.voiceId,voiceMediaId:this.audioData.voiceMediaId});
}
}
}
</script>
<style lang="scss" scoped>
@import '../../../assets/style/base/var.scss';
.audio-item{
border: 1px solid #e4e7ed;
width: 290px;
margin: 10px;
max-height: 111px;
overflow: hidden;
&:hover{
border: 1px solid #409eff;
}
&_top{
padding:10px;
&>div{
padding-left: 15px;
line-height: 20px;
}
}
&_bottom {
display: flex;
justify-content: space-around;
align-items: center;
height: 30px;
background: $gray-color;
border-top: 1px solid $border-color;
cursor: pointer;
i{
font-size: 16px;
&:hover{
color: #409eff;
}
}
}
}
</style>
<template>
<section class="dm-alib">
<header>
<span>语音列表共{{total}}</span>
<el-button type="primary" @click="add">新建语音</el-button>
</header>
<ul class="clearfix" v-loading="loading">
<dm-audio-item class="fl" v-for="(v,i) in audioList" :key="i" :audioData="v" @edit="edit" @del="del"></dm-audio-item>
</ul>
<div class="text-center" v-if="audioList.length === 0">
<img class="block block-center pt100" width="60" height="60" src="../../../assets/img/no-data_icon.png" alt="">
<el-button class="block block-center mt10" type="text" @click="add">新建语音</el-button>
</div>
<el-pagination v-show="audioList.length" background class="dm-pagination" @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-page="listParams.currentPage" :page-sizes="[10, 20, 30, 40]" :page-size="listParams.pageSize" layout="total, sizes, prev, pager, next" :total="total"></el-pagination>
<dm-audio-dialog :show.sync="audioDialogShow" :voiceId="currentVoiceId" @refresh="loadAudioList"></dm-audio-dialog>
</section>
</template>
<script>
import { loadAudioList , delAudioService } from "@/service/api/wechatApi.js";
import {formateDateTimeByType} from '@/utils/index.js'
import dmAudioItem from './audioItem';
import dmAudioDialog from './audioDialog';
export default {
created(){
this.loadAudioList();
},
data() {
return {
audioList:[],
listParams:{
currentPage:1,
pageSize:20
},
total:1,
loading:false,
audioDialogShow:false,
currentVoiceId:''
}
},
components: {
dmAudioItem,
dmAudioDialog
},
methods: {
handleSizeChange(val) {
this.listParams.pageSize = val;
},
handleCurrentChange(val) {
this.listParams.currentPage = val;
},
async loadAudioList() {
this.loading = true;
let res = await loadAudioList(this.listParams);
this.total = res.result.totalCount;
this.audioList = res.result.result ||[];
this.audioList.map(v => {
if (typeof v.createTime === 'number') {
v.createTimeStr = formateDateTimeByType(v.createTime,'yyyy-MM-dd-HH-mm-ss')
} else {
v.createTimeStr = ''
}
})
this.loading = false;
},
edit(val) {
this.currentVoiceId = val;
this.audioDialogShow = true;
},
add() {
this.currentVoiceId = '';
this.audioDialogShow = true;
},
del(val) {
this.$confirm('是否删除该语音?', '提示', {
confirmButtonText: '确定',
cancelBUttonText: '取消',
type: 'warning'
}).then(() => {
this.delAudioService(val);
}).catch(() => {
this.$tips({type: 'info',message: '已取消删除'});
});
},
async delAudioService(val) {
try {
this.loading = true;
let res = await delAudioService(val);
this.$tips({type:'success',message:'删除成功'});
this.loadAudioList();
this.loading = false;
} catch (err) {
this.$tips({type:'error',message:'删除失败'});
this.loading = false;
}
}
}
}
</script>
<style lang="scss" scoped>
.dm-alib {
header{
display: flex;
justify-content: space-between;
align-items: center;
}
}
</style>
<template>
<section class="sms-lib">
<div :class="pbSize">
<span class="pr10">选择卡券(共{{total}}条)</span>
<el-input v-model="listParams.searchParam" class="w200" clearable placeholder="请输入卡券名称" @change="getCardList"><i slot="prefix" class="el-input__icon el-icon-search"></i></el-input>
<span class="fz12 gray pl20">领取限制>1的卡券不支持选择,系统已过滤。 </span>
</div>
<el-table tooltipEffect="light" :data="smsTempList" :height="tableHeight" style="width: 100%" v-loading="loading" @row-click="chooseCard">
<el-table-column :show-overflow-tooltip="false" width="60" align="center" prop="coupCardId">
<template slot-scope="scope">
<div class="sms-record_left label-hidden">
<el-radio v-if="cardIdName === 'wechatCardId'" v-model="selectedId" :label="scope.row.wechatCardId" class="pr10"></el-radio>
<el-radio v-else v-model="selectedId" :label="scope.row.coupCardId" class="pr10"></el-radio>
</div>
</template>
</el-table-column>
<el-table-column :show-overflow-tooltip="false" :width="200" align="left" prop="cardName" label="卡券名称"></el-table-column>
<el-table-column :show-overflow-tooltip="false" :width="100" align="left" prop="cardLimit" label="领取限制"></el-table-column>
<el-table-column :show-overflow-tooltip="false" :width="120" align="left" prop="storeMode" label="适用门店">
<template slot-scope="scope">
{{scope.row.storeMode === 0?'所有门店':(scope.row.storeMode === 1?'部分分组':'部分门店')}}
</template>
</el-table-column>
<el-table-column :show-overflow-tooltip="false" :width="120" align="left" prop="couponStock" label="库存"></el-table-column>
<el-table-column :show-overflow-tooltip="true" :min-width="200" align="left" prop="subName" label="描述"></el-table-column>
</el-table>
<el-pagination v-show="smsTempList.length" background class="dm-pagination" @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-page="listParams.currentPage" :page-sizes="[10, 20, 30, 40]" :page-size="listParams.pageSize" layout="total, prev, pager, next" :total="total"></el-pagination>
</section>
</template>
<script>
import {getCardList} from '@/service/api/commonApi.js';
export default {
props:{
activeId:{
type:String,
default:''
},
pbSize:{
type:String,
default:'pb22'
},
cardIdName:{
type:String,
default:'coupCardId'
},
tableHeight:{
type:String,
default:'auto'
},
},
data(){
return{
listParams:{
searchParam:'',
currentPage:1,
pageSize:10,
requestProject:'gic-web',
cardLimitType:1,
cardType:''
},
total:0,
smsTempList:[],
selectedId:this.activeId
}
},
watch: {
selectedId(val) {
console.log('222=>>>>>>>>>>>>>>>'+val)
if (this.cardIdName === 'wechatCardId') {
this.smsTempList.map(v => {
if (v.coupCardId === val) {
val = v.wechatCardId
}
})
}
this.$emit('update:activeId',val);
},
activeId(val) {
this.selectedId = val;
}
},
created(){
this.selectedId = this.activeId;
this.getCardList();
},
methods:{
handleSizeChange(val) {
this.listParams.pageSize = val;
this.getCardList();
},
handleCurrentChange(val) {
this.listParams.currentPage = val;
this.getCardList();
},
async getCardList() {
this.loading = true;
let res = await getCardList(this.listParams);
this.smsTempList = res.result.result || [];
this.total = res.result.totalCount;
this.loading = false;
},
reset() {
this.listParams.searchParams = '';
this.getCardList();
},
chooseCard(row) {
this.selectedId = row.coupCardId;
$bus.$emit('card-temp-choose',row);
}
}
}
</script>
<template>
<label :name="imgData.imageId" class="dm-img-item">
<div class="dm-img-item_top" @mouseover="optShow = true" @mouseout="optShow = false">
<img :src="imgData.qcloudImageUrl" alt="" srcset="">
<div class="dm-img-item_opt" v-show="optShow">
<i class="fz14 el-icon-edit cursor" @click.stop.prevent="edit"></i>
<i class="iconfont icon-fenzu cursor" @click.stop.prevent="move"></i>
<i class="fz14 el-icon-delete cursor" @click.stop.prevent="del"></i>
</div>
</div>
<div class="dm-img-item_center">
<el-checkbox :label="imgData.imageId">{{imgData.imageTitle}}</el-checkbox>
</div>
</label>
</template>
<script>
import {updateImgName} from '@/service/api/wechatApi.js';
export default {
props:{
imgData:{
type:Object,
default:() => {
return {
qcloudImageUrl:'',
imageId:'',
imageTitle:''
}
}
}
},
data() {
return {
optShow:false
}
},
methods:{
edit() {
this.$prompt('', '修改图片名称', {
confirmButtonText: '确定',
cancelBUttonText: '取消',
inputPattern: /\S/,
inputPlaceholder:'请输入新名称',
inputErrorMessage: '名称不能为空',
inputValue:this.imgData.imageTitle,
}).then(({ value }) => {
this.updateImgName(value);
}).catch(() => {
this.$tips({
type: 'info',
message: '取消修改'
});
});
},
del(val) {
this.$emit('delSingleImg',this.imgData.imageId);
},
move() {
this.$emit('moveSingleImg',this.imgData.imageId);
},
async updateImgName(val) {
let res = await updateImgName({imageId:this.imgData.imageId,imageTitle:val});
this.$tips({type: 'success',message: '修改成功'});
this.$emit('refresh');
}
}
}
</script>
<style lang="scss" scoped>
@import '../../../assets/style/base/var.scss';
@import '../../../assets/style/mixin/index.scss';
.dm-img-item{
width: 168px;
margin: 20px 10px 0 10px;
float: left;
border: 1px solid #e4e7ed;
overflow: hidden;
cursor: pointer;
&:hover{
border: 1px solid #409eff;
}
&_top{
position: relative;
width: 168px;
height: 168px;
img{
width: 100%;
height: 100%;
}
}
&_center{
border-top:1px solid $border-color;
height: 32px;
display: flex;
justify-content: left;
align-items: center;
padding-left: 10px;
/deep/ .el-checkbox__label {
text-overflow: ellipsis;
overflow: hidden;
max-width: 130px;
white-space: nowrap;
word-wrap: normal;
width: auto;
vertical-align: middle;
padding-left: 10px;
}
}
&_opt{
position: absolute;
bottom: 0;
left: 0;
width: 100%;
// border-top:1px solid $border-color;
background: rgba(0,0,0,.5);
height: 32px;
display: flex;
justify-content: space-around;
align-items: center;
color: #fff;
i {
&:hover{
color:#409eff;
}
}
.el-icon-delete{
&:hover{
color:#f56c6c;
}
}
}
}
</style>
<template>
<label :name="imgData.imageMediaId" class="dm-img-item" :class="{'active':active === imgData.imageId}" @click="chooseImg">
<div class="dm-img-item_top">
<img :src="imgData.qcloudImageUrl" alt="" srcset="">
</div>
<div class="dm-img-item_center">
<el-checkbox :label="imgData.imageMediaId">{{imgData.imageTitle}}</el-checkbox>
</div>
</label>
</template>
<script>
export default {
props:{
imgData:{
type:Object,
default:() => {
return {
qcloudImageUrl:'',
imageId:'',
imageTitle:''
}
}
}
},
data() {
return {
active:''
}
},
methods:{
chooseImg() {
$bus.$emit('img-temp-choose',this.imgData);
}
}
}
</script>
<style lang="scss" scoped>
@import '../../../assets/style/base/var.scss';
@import '../../../assets/style/mixin/index.scss';
.dm-img-item{
width: 168px;
margin: 20px 10px 0 10px;
float: left;
border: 1px solid #e4e7ed;
overflow: hidden;
cursor: pointer;
&:hover{
border: 1px solid #409eff;
}
&_top{
width: 168px;
height: 168px;
position: relative;
img{
width: 100%;
height: 100%;
}
}
&_center{
border-top:1px solid $border-color;
height: 32px;
display: flex;
justify-content: left;
align-items: center;
padding-left: 10px;
/deep/ .el-checkbox__label{
@include ellipsis(130px);
vertical-align: middle;
padding-left: 10px;
}
}
&_bottom{
border-top:1px solid $border-color;
height: 32px;
display: flex;
justify-content: space-around;
align-items: center;
background: $gray-color;
}
}
</style>
<template>
<section class="dm-ilib clearfix">
<nav class="fl">
<h4 class="text-left pl10" ><el-button style="font-weight: 500;" size="small" type="text" icon="el-icon-plus" @click.stop="addGroup">新建分组</el-button></h4>
<ul class="dm-ilib-category" :style="scrollStyle">
<li v-for="(v,i) in groupsList" :key="i" @click="changeCate(v)" :class="{'active':listParams.wechatImageGroupId===v.wechatImageGroupId}"><span class="ellipsis-80">{{v.groupName}}</span><span class="fz10 gray"> ({{v.imageCount}})</span></li>
</ul>
</nav>
<article class="fr">
<div class="dm-ilib-header">
<h4>{{listParams.wechatImageGroupId?currentGroup.groupName:'所有图片'}}</h4>
<div class="dm-ilib-opt_right">
<span class="fz12 gray pr10">大小不超过5M</span>
<label class="el-button el-button--primary el-button--small" :class="{'is-disabled':upLoadDisabled}">{{upLoadDisabled?'上传中...':'上传图片'}}
<input type="file" style="display:none;" :disabled="upLoadDisabled" accept="image/gif, image/jpeg,image/png" ref="uploader" v-imglibupload='this'>
</label>
</div>
</div>
<el-checkbox-group v-loading="loading" v-model="newMediaId" :max="1" class="clearfix img-item__wrap" :style="scrollStyle">
<img-item-radio v-for="(v,i) in imgList" :key="i" :imgData="v" @click.native.stop.prevent="selectImg(v)"></img-item-radio>
</el-checkbox-group>
<el-pagination v-show="imgList.length" background class="dm-pagination imglib-pagination" @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-page="listParams.currentPage" :page-sizes="[10, 20, 30, 40]" :page-size="listParams.pageSize" layout="total, sizes, prev, pager, next" :total="total"></el-pagination>
</article>
</section>
</template>
<script>
import imgItemRadio from './imgItemRadio'
import {loadImgList,updateGroupName,addGroupService,deleteGroupService,deleteImageService,changeGroupService} from '@/service/api/wechatApi.js';
export default {
name:'dm-ilib',
props:{
activeName:String,
scrollStyle:Object,
mediaId:String
},
created(){
this.loadImgList();
this.newMediaId = [this.mediaId];
},
watch:{
mediaId(val) {
console.log(val)
// this.newMediaId = val;
}
},
data() {
return {
upLoadDisabled:false,
imgList:[],
listParams:{
currentPage:1,
pageSize:10,
wechatImageGroupId:''
},
total:0,
newMediaId:[],
groupsList:[],
groupsMoveList:[],
loading:true,
currentGroup:{groupName:''},
moveImgVal:'',
fileList: [],
}
},
components: {
imgItemRadio
},
methods:{
selectImg(row) {
this.newMediaId.splice(0,1);
this.newMediaId.push(row.imageMediaId);
this.$emit('get-data',this.newMediaId[0])
},
handleSizeChange(val) {
this.listParams.pageSize = val;
this.loadImgList();
},
handleCurrentChange(val) {
this.listParams.currentPage = val;
this.loadImgList();
},
changeCate(v){
this.listParams.wechatImageGroupId = v.wechatImageGroupId;
this.loadImgList();
},
async loadImgList() {
this.loading = true;
let res = await loadImgList(this.listParams);
this.total = res.result.page.totalCount;
this.currentGroup = res.result.currentGroup;
this.groupsMoveList = Object.assign([],res.result.groups);
this.groupsList = res.result.groups;
this.groupsList.unshift({wechatImageGroupId: "",groupName: "所有图片",imageCount: res.result.totalPicsCount })
this.imgList = [];
if (res.result.page.result) {
const resList = res.result.page.result;
this.$nextTick(_ => {
resList.map(v => {
this.imgList.push(v)
})
})
} else {
this.imgList = [];
}
this.loading = false;
},
//图片上传相关
handleRemove(file, fileList) {
console.log(file, fileList);
},
handlePreview(file) {
console.log(file);
},
handleExceed(files, fileList) {
this.$message.warning(`当前限制选择 3 个文件,本次选择了 ${files.length} 个文件,共选择了 ${files.length + fileList.length} 个文件`);
},
beforeRemove(file, fileList) {
return this.$confirm(`确定移除 ${ file.name }?`);
},
uploadSuccess(response,file,fileList) {
if (response.errorCode === 0) {
this.$tips({type:'warning',message:'上传成功'});
this.fileList = []
this.loadImgList();
} else {
this.$tips({type:'warning',message:response.message});
}
},
addGroup() {
this.$prompt('', '新增分组', {
confirmButtonText: '确定',
cancelBUttonText: '取消',
inputPattern: /\S/,
inputPlaceholder:'请输入分组名称',
inputErrorMessage: '名称不能为空',
inputValue:'新分组',
}).then(({ value }) => {
this.addGroupService(value);
}).catch(err => {
console.log(err)
this.$tips({
type: 'info',
message: '取消新增'
});
});
},
async addGroupService(val) {
let res = await addGroupService({groupName:val});
this.$tips({type: 'success',message: '新增成功'});
this.loadImgList();
},
}
}
</script>
<style lang="scss" scoped>
@import '../../../assets/style/base/var.scss';
@import '../../../assets/style/mixin/index.scss';
.dm-ilib{
border: 1px solid $border-color;
border-radius: 4px;
&>article{
position:relative;
width: calc(80% - 1px);
border-left: 1px solid $border-color;
border-radius:0 4px 4px 0;
min-height:573px;
// border-bottom: 1px solid $border-color;
.dm-ilib-header{
display: flex;
justify-content: space-between;
padding:0 16px;
align-items: center;
border-bottom: 1px solid $border-color;
}
h4{
height: 50px;
line-height: 50px;
font-size: 16px;
i{
font-size: 20px;
padding-left:10px;
cursor: pointer;
}
}
}
.img-item__wrap {
min-height: 456px;
}
&>nav{
width: 20%;
h4{
height: 50px;
line-height: 50px;
padding-right: 20px;
font-size: 16px;
border-bottom: 1px solid $border-color;
cursor: pointer;
}
.dm-ilib-category{
height: 522px;
overflow-y: scroll;
cursor: pointer;
li{
height: 40px;
line-height: 40px;
// border-bottom:1px dashed $border-color;
padding-left: 10px;
&.active{
background: $gray-color;
}
&:hover{
background: $gray-color;
}
}
}
}
&-opt{
background: $gray-color;
display: flex;
justify-content: space-between;
align-items: center;
height: 60px;
padding:0 20px;
}
}
.ellipsis-80{
@include ellipsis(80px);
display: inline-block;
vertical-align: middle;
}
.dm-upload{
display: inline-block;
position: relative;
}
.imglib-pagination{
// position:absolute;
// bottom:7px;
padding-right:20px;
}
</style>
<template>
<el-dialog title="移动图片" :visible.sync="show" width="30%" :before-close="close" v-loading="loading">
<el-select v-model="radio" placeholder="请选择">
<el-option
v-for="item in dataList"
:key="item.wechatImageGroupId"
:label="item.groupName"
:value="item.wechatImageGroupId">
</el-option>
</el-select>
<span slot="footer" class="dialog-footer">
<el-button type="primary" @click="submit">确 定</el-button>
<el-button @click="close">取 消</el-button>
</span>
</el-dialog>
</template>
<script>
export default {
props:{
show:{
type:Boolean,
default:false
},
dataList:{
type:Array,
default:[]
},
},
data() {
return {
loading:false,
radio:''
};
},
methods: {
close() {
this.$emit('update:show',false);
this.radio = ''
},
submit() {
if (!this.radio) {
this.$tips({type:'warning',message:'请选择移入选择分组'});
return;
}
this.$emit('moveFinish',this.radio);
this.radio = ''
}
}
};
</script>
<template>
<section>
<div class="dm-imgtext_head" :class="pbSize">
<div>图文消息(共{{total}}条) <el-input v-model="listParams.searchName" clearable class="w200 ml10" placeholder="请输入标题/作者" @change="loadImgTextList"><i slot="prefix" class="el-input__icon el-icon-search"></i></el-input></div>
<div><el-button v-if="auto" type="primary" @click="check">检查同步</el-button><el-button type="primary" @click="add">新建图文</el-button></div>
</div>
<el-radio-group class="dm-imgtext-list" v-model="selectedData" v-loading="loading" :style="scrollStyle">
<el-table tooltipEffect="light" :data="textImgList" style="width: 100%" @row-click="rowClick">
<el-table-column label="" align="center" width="55px" v-if="radioShow">
<template slot-scope="scope" >
<div class="label-hidden"><el-radio :label="scope.row"></el-radio></div>
</template>
</el-table-column>
<el-table-column label="图片" align="left" width="120px">
<template slot-scope="scope" >
<img width="100" height="100" :src="scope.row.itemList.length?(scope.row.itemList[0].qcloudImageUrl || ''):''" alt="">
</template>
</el-table-column>
<el-table-column label="图文标题" align="left" min-width="100px">
<template slot-scope="scope" >
<a class="blue" :href="scope.row.itemList.length?(scope.row.itemList[0].mediaUrl || ''):''" target="_blank">{{scope.row.itemList.length?(scope.row.itemList[0].titleName || ''):''}}</a>
</template>
</el-table-column>
<el-table-column label="作者" prop="updateTimeStr" align="left" width="120px">
<template slot-scope="scope" >
<p>{{scope.row.itemList.length?(scope.row.itemList[0].authorName || '--'):''}}</p>
</template>
</el-table-column>
<el-table-column label="修改时间" prop="updateTimeStr" align="left" width="160px">
<template slot-scope="scope" >
{{scope.row.updateTimeStr}}
</template>
</el-table-column>
<el-table-column label="操作" align="left" width="200px" v-if="auto">
<template slot-scope="scope" >
<el-button type="text" @click="editData(scope.row)">编辑</el-button>
<el-button type="text" @click="delData(scope.row)">删除</el-button>
<el-button type="text" @click="$router.push('/wechat/record/add/'+scope.row.imageTextId)">使用</el-button>
</template>
</el-table-column>
</el-table>
</el-radio-group>
<el-pagination v-show="textImgList.length" background class="dm-pagination" @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-page="listParams.currentPage" :page-sizes="[10, 20, 30, 40]" :page-size="listParams.pageSize" layout="total, sizes, prev, pager, next" :total="total"></el-pagination>
<sync-imgtext :show.sync="syncImgTextShow"></sync-imgtext>
</section>
</template>
<script>
import {loadImgTextList,deleteImageText} from '@/service/api/wechatApi.js';
import {formateDateTimeByType} from '@/utils/index.js'
import syncImgtext from './syncImgtext';
export default {
props:{
scrollStyle:Object,
auto:{
type:Boolean,
default:true
},
radioShow:{
type:Boolean,
default:false
},
activeId:String,
pbSize:{
type:String,
default:'pb22'
},
},
created(){
this.loadImgTextList();
},
data() {
return {
textImgList:[],
listParams:{
currentPage:1,
pageSize:20,
searchName:''
},
total:0,
loading:false,
syncImgTextShow:false,
selectedImgTextId:'',
selectedData:{}
}
},
components:{
syncImgtext
},
watch:{
},
methods:{
async loadImgTextList() {
this.loading = true
try {
let res = await loadImgTextList(this.listParams);
if (res.errorCode === 0) {
this.textImgList = res.result.result || [];
this.total = res.result.totalCount;
this.textImgList.map(v => {
v.createTimeStr = formateDateTimeByType(v.createTime,'yyyy-MM-dd-HH-mm')
v.updateTimeStr = formateDateTimeByType(v.updateTime,'yyyy-MM-dd-HH-mm')
if (this.activeId === v.imageTextWechatId) {
this.selectedData = v;
}
})
}
} catch(err) {
console.log(err)
this.$tips({type:'warning',message:'列表请求出错,请稍后再试'});
}
this.loading = false
},
reset() {
this.listParams.searchName = '';
this.loadImgTextList();
},
check() {
this.syncImgTextShow = true;
},
rowClick(row) {
console.log(row)
this.selectedData = row;
this.$emit('get-data',{imageTextId:row.imageTextId,imageTextWechatId:row.imageTextWechatId})
},
add () {
this.$router.push('/wechat/editor');
},
editData(row) {
this.$router.push('/wechat/editor/'+row.imageTextId);
},
delData(row) {
this.$confirm('确认要删除?', '提示', {
confirmButtonText: '确定',
cancelBUttonText: '取消',
type: 'warning'
}).then(() => {
deleteImageText({imageTextId:row.imageTextId}).then(res => {
this.$tips({type: 'success',message: '删除成功!'});
this.loadImgTextList();
}).catch(err => {
this.$tips({type: 'error',message: '删除失败!'});
})
}).catch(() => {
this.$tips({type: 'info',message: '已取消删除'});
});
},
handleSizeChange(val) {
this.listParams.pageSize = val;
this.loadImgTextList();
},
handleCurrentChange(val) {
this.listParams.currentPage = val;
this.loadImgTextList();
}
}
}
</script>
<style lang="scss" scoped>
.dm-imgtext_head{
display: flex;
justify-content: space-between;
}
.dm-imgtext-list {
width: 100%;
&>.dm-imgtext-item {
display: flex;
justify-content: space-between;
padding: 10px;
border-bottom: 1px solid #e7e7eb;
}
.dm-imgtext_left{
&>img {
width: 100px;
height: 100px;
display:inline-block;
vertical-align: middle;
box-shadow: 0 2px 12px 0 rgba(0,0,0,.1);
}
&>div{
display: inline-block;
vertical-align: middle;
line-height: 26px;
padding-left: 10px;
}
}
}
</style>
<template>
<el-dialog title="同步微信图文" :visible.sync="show" width="30%" :before-close="close" v-loading="loading">
<div class="gic-modal-bd text-center">
<div class="gic-modal-content">
<p class="fz16" style="margin-bottom:20px">微信图文共<span> {{count}} </span><span @click="getSyncInfo" class="blue" style="margin-left:10px;">刷新</span></p>
<p class="gray fz12">同步全部商品可能会需要比较久的时间,请耐心等待</p>
</div>
</div>
<div class="text-center pt20">
<el-select class="w100" v-if="loadStatus === 1 && count!==0" v-model="size" placeholder="选择群发类型">
<el-option v-for="item in sizeOptions" :key="item.value" :label="item.label" :value="item.value"></el-option>
</el-select>
<el-button v-if="loadStatus === 1" type="primary" @click="syncWechat()">立即同步</el-button>
<el-button v-if="loadStatus === 0" type="primary" :loading="true">同步中</el-button>
<el-button v-if="loadStatus === 3" icon="el-icon-error" type="danger" >同步失败</el-button>
</div>
</el-dialog>
</template>
<script>
import {getSyncInfo,syncWechatImageText} from '@/service/api/wechatApi.js';
export default {
props:{
show:{
type:Boolean,
default:false
}
},
watch: {
show(val) {
if (val) {
this.getSyncInfo();
}
}
},
data() {
return {
loadStatus:1,
loading:false,
count:0,
size:5,
sizeOptions:[{label:'5条',value:5},{label:'10条',value:10},{label:'20条',value:20},{label:'全部',value:999999}]
};
},
methods: {
close() {
this.$emit('update:show',false);
},
async getSyncInfo() {
this.loading = true;
let res = await getSyncInfo();
this.count = res.result.wechatCount;
this.loadStatus = res.result.status;
this.loading = false;
},
async syncWechat(){
this.loadStatus = 0;
const num = this.size === 999999?this.count:this.size
let res = await syncWechatImageText({num});
if(num <= 20 && res.errorCode === 0) {
this.$tips({type:'success',message:"同步成功"});
this.loadStatus = 1;
this.close();
}
}
}
};
</script>
<style lang="scss" scoped>
.gic-modal-content {
font-size: 18px;
color: #333;
margin-top: 30px;
font-size: 16px;
}
</style>
<template>
<section class="sms-lib">
<div :class="pbSize">
<span class="pr10">选择短信模板(共{{total}}条)</span><el-input clearable v-model="listParams.search" class="w200" placeholder="请输入标题/作者" @change="LoadTempList"><i slot="prefix" class="el-input__icon el-icon-search"></i></el-input>
</div>
<el-table tooltipEffect="light" :data="smsTempList" style="width: 100%" v-loading="loading" @row-click="rowClick">
<el-table-column :show-overflow-tooltip="false" :width="60" align="center" prop="smsTemplateId">
<template slot-scope="scope">
<div class="sms-record_left label-hidden">
<el-radio v-model="activeSmsId" :label="scope.row.smsTemplateId" class="pr10"></el-radio>
</div>
</template>
</el-table-column>
<el-table-column :show-overflow-tooltip="false" :width="200" :min-width="200" align="left" prop="title" label="模板名称"></el-table-column>
<el-table-column :show-overflow-tooltip="false" :width="200" :min-width="200" align="left" prop="content" label="模板类型">
<template slot-scope="scope">
<p class="gray">{{scope.row.type === 0 ?'普通短信' :(scope.row.type === 1?'营销短信':'验证码')}}</p>
</template>
</el-table-column>
<el-table-column :show-overflow-tooltip="false" :min-width="200" align="left" prop="content" label="模板内容">
<template slot-scope="scope">
<div>{{scope.row.content}}</div>
</template>
</el-table-column>
</el-table>
<el-pagination v-show="smsTempList.length" background class="dm-pagination" @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-page="listParams.currentPage" :page-sizes="[10, 20, 30, 40]" :page-size="listParams.pageSize" layout="total, sizes, prev, pager, next" :total="total"></el-pagination>
</section>
</template>
<script>
import {LoadTempList} from '@/service/api/messageApi.js';
export default {
props:{
pbSize:{
type:String,
default:'pb22'
},
activeId:String,
},
data(){
return{
listParams:{
search:'',
currentPage:1,
pageSize:20
},
total:0,
smsTempList:[],
activeSmsId:''
}
},
watch:{
activeId(val) {
this.activeSmsId = val;
console.log(val)
}
},
created(){
this.LoadTempList();
},
methods:{
rowClick(row) {
this.activeSmsId = row.smsTemplateId;
this.$emit('update:activeId',row.smsTemplateId);
},
handleSizeChange(val) {
this.listParams.pageSize = val;
this.LoadTempList();
},
handleCurrentChange(val) {
this.listParams.currentPage = val;
this.LoadTempList();
},
async LoadTempList() {
this.loading = true;
let res = await LoadTempList(this.listParams);
this.smsTempList = res.result.result || [];
this.total = res.result.totalCount;
this.loading = false;
},
reset() {
this.listParams.search = '';
this.LoadTempList();
},
}
}
</script>
<template>
<section>
<el-form ref="form" :model="form" :rules="rules" label-width="80px" v-loading="loading">
<el-form-item label="标题" prop="titleName" class="w450">
<dm-input v-model="form.titleName" :maxlength="64"></dm-input>
</el-form-item>
<el-form-item label="分类" class="w450">
<el-select v-model="form.videoCategory">
<el-option v-for="item in dictList"
:key="item.dict_id"
:label="item.dict_name"
:value="item.dict_id"></el-option>
</el-select>
</el-form-item>
<el-form-item label="标签" prop="videoTags" class="w450">
<dm-input v-model="form.videoTags" :maxlength="10"></dm-input>
</el-form-item>
<el-form-item label="简介" prop="remark" class="w450">
<dm-input type="textarea" v-model="form.remark" :rows="6" :maxlength="120"></dm-input>
</el-form-item>
<el-form-item label="视频内容" v-if="!this.videoId">
<el-upload v-show="form.remark && form.titleName"
class="dm-upload_right"
:action="api+'/api-marketing/upload-marketing-videosave'"
:on-preview="handlePreview"
:on-remove="handleRemove"
:before-remove="beforeRemove"
enctype="multipart/form-data"
:on-success="uploadSuccess"
:auto-upload="true"
:multiple="false"
:limit="1"
:data="{titleName:form.titleName,remark:form.remark,requestProject:'marketing'}"
:on-exceed="handleExceed"
:file-list="fileList"
>
<el-button size="small" type="primary" :disabled="!(form.remark && form.titleName)">上传视频</el-button>
</el-upload>
<el-button size="small" v-show="!(form.remark && form.titleName)" type="primary" @click="tips">上传视频</el-button>
<p class="gray fz12 line-height2">视频不能超过20M,支持大部分主流视频格式,超过20M的视频可至<a href="http://v.qq.com/" target="_blank">腾讯视频</a>上传后添加</p>
</el-form-item>
<p class="gray text-center pb10 pt20">不得上传未经授权的他人作品,以及色情、反动等违法视频。</p>
<p class="gray text-center"><el-checkbox v-model="checked" class="pr10"></el-checkbox>我已阅读并同意<a href="http://v.qq.com/help/help_agreement.html" target="_blank">《腾讯视频上传服务规则》</a></p>
</el-form>
<div class="btn-wrap_fixed" :class="{'on':asideShow}">
<el-button type="primary" :disabled="!checked" @click="submit('form')">确 定</el-button>
<el-button @click="$router.go(-1)">返 回</el-button>
</div>
</section>
</template>
<script>
import config from '@/config';
import { editVideoDetailService ,uploadVideoService,saveVideoService} from "@/service/api/wechatApi.js";
export default {
created() {
this.editVideoDetailService();
if (this.$route.meta.type === 'edit') {
this.checked = true;
}
},
data() {
return {
api:config.api,
loading:false,
radio:'',
dictList:[],
form:{titleName:'',videoCategory:'',videoTags:'',remark:''},
fileList: [],
rules:{
titleName:[{required:true, message: '请输入文件名', trigger: 'change' }],
remark:[{required:true, message: '请输入文件名', trigger: 'change' }]
},
videoMediaId:'',
videoUrl:'',
checked:false,
videoId:this.$route.params.id
};
},
computed:{
asideShow() {
return this.$store.state.marketing.asideShow
}
},
methods: {
tips() {
this.$tips({type:'warning',message:'请先填写标题和简介再进行上传'})
},
submit(formName) {
if (!this.videoId && !this.videoMediaId) {
this.$tips({type:'warning',message:'请上传视频文件再提交'});
return;
}
this.$refs[formName].validate((valid) => {
if (valid) {
this.saveVideoService();
} else {
console.log('error submit!!');
return false;
}
});
},
async editVideoDetailService() {
try {
this.loading = true;
let res = await editVideoDetailService({videoId:this.videoId});
this.dictList = res.result.gicDictDtoList;
this.form.titleName = res.result.wechatVideoDTO ? res.result.wechatVideoDTO.titleName : '';
this.form.videoCategory = res.result.wechatVideoDTO ?res.result.wechatVideoDTO.videoCategory :'';
this.form.videoTags = res.result.wechatVideoDTO ? res.result.wechatVideoDTO.videoTags :'';
this.form.remark = res.result.wechatVideoDTO ? res.result.wechatVideoDTO.remark :'';
this.videoMediaId = res.result.wechatVideoDTO ? res.result.wechatVideoDTO.videoMediaId : '';
this.videoUrl = res.result.wechatVideoDTO ? res.result.wechatVideoDTO.videoUrl : '';
this.loading = false;
} catch (err) {
console.log(err)
this.$tips({type:'error',message:'初始化失败,请稍后再试'});
this.loading = false;
}
},
//保存操作
async saveVideoService() {
try {
this.loading = true;
let params = {
videoId:this.videoId,
titleName:this.form.titleName,
videoTags:this.form.videoTags,
videoCategory:this.form.videoCategory,
remark:this.form.remark,
videoMediaId:this.videoMediaId,
videoUrl:this.videoUrl
}
let res = await saveVideoService(params);
if (res.errorCode === 0) {
this.loading = false;
this.$tips({type:'success',message:'保存视频成功'});
this.$router.go(-1)
} else {
this.$tips({type:'error',message:'保存视频失败'});
}
} catch (err) {
this.loading = false;
this.$tips({type:'error',message:'保存视频失败'});
}
},
//上传相关
handleRemove(file, fileList) {
console.log(file, fileList);
},
handlePreview(file) {
console.log(file);
},
handleExceed(files, fileList) {
this.$message.warning(`只能上传一个文件,请先移除文件`);
},
beforeRemove(file, fileList) {
return this.$confirm(`确定移除 ${ file.name }?`);
},
uploadSuccess(response,file,fileList) {
if (response.errorCode === 0) {
this.$tips({type:'warning',message:'上传成功'});
this.videoMediaId = response.result.media_id;
this.videoUrl = response.result.url;
} else {
this.$tips({type:'warning',message:response.message});
}
},
}
};
</script>
<template>
<section class="dm-vlib">
<header class="pb10">
<span>视频列表共{{total}}</span>
<el-button type="primary" @click="$router.push('/wechat/temp/video/add')" v-if="!chooseFlag">新建视频</el-button>
</header>
<ul class="clearfix" v-loading="loading" :style="scrollStyle">
<dm-video-item :chooseFlag="chooseFlag" class="fl" v-for="(v,i) in videoList" :key="i" :videoData="v" @edit="edit" @del="del"></dm-video-item>
</ul>
<div class="text-center" v-if="videoList.length === 0">
<img class="block block-center pt100" width="60" height="60" src="../../../assets/img/no-data_icon.png" alt="">
<el-button class="block block-center mt10" type="text" @click="$router.push('/wechat/temp/video/add')">新建视频</el-button>
</div>
<el-pagination v-show="videoList.length" background class="dm-pagination" @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-page="listParams.currentPage" :page-sizes="[10, 20, 30, 40]" :page-size="listParams.pageSize" layout="total, sizes, prev, pager, next" :total="total"></el-pagination>
</section>
</template>
<script>
import { loadVideoList , delVideoService } from "@/service/api/wechatApi.js";
import {formateDateTimeByType} from '@/utils/index.js'
import dmVideoItem from './videoItem';
export default {
props: {
mediaId:{
type:String,
default:''
},
chooseFlag:{
type:Boolean,
default:false
},
scrollStyle:Object,
},
created(){
this.loadVideoList();
},
data() {
return {
videoList:[],
listParams:{
currentPage:1,
pageSize:20
},
total:1,
loading:false
}
},
components: {
dmVideoItem
},
methods: {
handleSizeChange(val) {
this.listParams.pageSize = val;
},
handleCurrentChange(val) {
this.listParams.currentPage = val;
},
async loadVideoList() {
this.loading = true;
let res = await loadVideoList(this.listParams);
this.total = res.result.totalCount;
this.videoList = res.result.result || [];
this.videoList.map(v => {
if (typeof v.createTime === 'number') {
v.createTimeStr = formateDateTimeByType(v.createTime,'yyyy-MM-dd-HH-mm-ss')
} else {
v.createTimeStr = ''
}
})
this.loading = false;
},
edit(val) {
this.$router.push('/wechat/temp/video/edit/'+val)
},
del(val) {
this.$confirm('是否删除该视频?', '提示', {
confirmButtonText: '确定',
cancelBUttonText: '取消',
type: 'warning'
}).then(() => {
this.delVideoService(val);
}).catch(() => {
this.$tips({type: 'info',message: '已取消删除'});
});
},
async delVideoService(val) {
try {
this.loading = true;
let res = await delVideoService(val);
this.$tips({type:'success',message:'删除成功'});
this.loadVideoList();
this.loading = false;
} catch (err) {
this.$tips({type:'error',message:'删除失败'});
this.loading = false;
}
}
}
}
</script>
<style lang="scss" scoped>
.dm-vlib {
header{
display: flex;
justify-content: space-between;
align-items: center;
}
}
</style>
<template>
<li class="video-item" :class="{'active':currentId === mediaId}" @click="choose">
<div class="video-item_top clearfix">
<div>
<h5 class="ellipsis-100">{{videoData.titleName || '未命名视频'}}</h5>
<p class="gray fz12 ellipsis-100">{{videoData.createTimeStr}}</p>
</div>
</div>
<div class="video-item_center clearfix">
<img class="fl" src="../../../assets/img/video_img.jpg" alt="" srcset="">
<p class="fl gray fz12 ellipsis-100">{{videoData.remark}}</p>
</div>
<div class="video-item_bottom" v-if="!chooseFlag">
<i class="el-icon-edit" @click="edit"></i>
<i class="el-icon-delete" @click="del"></i>
</div>
</li>
</template>
<script>
export default {
props: {
videoData: {
type: Object,
default: function () {
return {
}
}
},
chooseFlag:{
type:Boolean,
default:false
},
mediaId:String,
},
data(){
return{
currentId:''
}
},
methods: {
choose(){
$bus.$emit('video-temp-choose',this.videoData)
},
edit(){
this.$emit('edit',this.videoData.videoId);
},
del() {
this.$emit('del',{videoId:this.videoData.videoId,videoIdMediaId:this.videoData.videoIdMediaId});
}
}
}
</script>
<style lang="scss" scoped>
@import '../../../assets/style/base/var.scss';
.video-item{
border: 1px solid #e4e7ed;
width: 290px;
margin: 10px;
overflow: hidden;
cursor: pointer;
&:hover{
border: 1px solid #409eff;
}
&.active{
border: 2px solid $danger-color;
}
&_top {
padding: 5px 10px 0 10px;
line-height: 20px;
}
&_center{
padding:10px;
height: 180px;
&>img{
width: 270px;
height: 160px;
}
&>div{
padding-left: 15px;
line-height: 20px;
}
&>p {
line-height: 30px;
}
}
&_bottom {
display: flex;
justify-content: space-around;
align-items: center;
height: 30px;
background: $gray-color;
border-top: 1px solid $border-color;
cursor: pointer;
i{
font-size: 16px;
&:hover{
color: #409eff;
}
}
}
}
</style>
<template>
<div style="display:inline-block;">
<el-popover class="store__popover" placement="bottom" width="200" trigger="click" v-model="popoverShow">
<el-input slot="reference" suffix-icon="el-icon-arrow-down" :value="newModel.label" :placeholder="newModel.label?'':placeholder"></el-input>
<el-tree
style="height: 260px;overflow-y: auto;"
ref="tree"
node-key="id"
:data="treeData"
:highlight-current="true"
@node-click="handleNodeClick">
</el-tree>
<div class="store-btn-wrap">
<span @click="close" class="cursor fz12 pr10 vertical-middle">取消</span>
<el-button size="mini" type="text" @click="submit">确定</el-button>
</div>
</el-popover>
</div>
</template>
<script>
import {storeGroupList} from '@/service/api/commonApi.js'
export default {
props:{
placeholder:{
type:String,
default:'请选择门店分组'
},
model:{
type:Object,
default(){
return {}
}
},
},
data() {
return {
popoverShow:false,
treeData:[],
newModel:Object.assign({},this.model)
}
},
created(){
this.storeGroupList();
},
methods:{
async storeGroupList() {
let res = await storeGroupList();
const result = res.result;
function _rec(list) {
list.map(v => {
v.label = v.storeGroupName;
v.id = v.storeGroupId;
if (v.children instanceof Array && v.children.length>0) {
_rec(v.children);
}
})
}
_rec(result);
this.treeData = result;
this.$nextTick(_ => {
this.$refs.tree.setCurrentKey(this.newModel.id);
})
},
handleNodeClick(data,node,comp){
this.newModel = data;
},
submit(){
this.popoverShow = false;
this.$emit('commit-store',this.newModel);
this.$store.commit('mutations_setStoreObj',this.newModel);
},
close(){
this.popoverShow = false;
}
}
}
</script>
<style lang="scss" scoped>
.store__popover{
padding: 0;
}
.store-btn-wrap{
text-align:right;
border-top:1px solid #ebeef5;
padding-top:10px;
}
</style>
<template>
 <div>
  <span
    :sendSync="sendSync"
    :autoStart="autoStart"
    :defaultVal="defaultVal"
  >{{countString}}</span>
 </div>
</template>
<script>
export default {
name:'time-count',
data() {
return {
isStart: false,
globalTimer: null, //获取setInterval对象值
countString: "0秒", //用来显示时间
day: 0,
hour: 0,
minute: 0,
second: 0,
millisecond: 0,
countVal: this.defaultVal, //获取初始值
pauseTime: 0
};
},
watch: {
countString: {
deep: true,
handler: function(val, oldVal) {
var vm = this;
if (vm.needSendSunc) {
vm.passToParent(val);
}
}
},
needSendSunc: {
deep: true,
handler: function(val) {
var vm = this;
if (val) {
vm.passToParent(vm.countString);
}
}
}
},
props: {
sendSync: {
type: Boolean,
default: false
},
autoStart: {
type: Boolean,
default: false
},
defaultVal: {
type: Number,
default: 0
}
},
mounted() {
var vm = this;
if (vm.autoStart) {
vm.startCountFn();
}
},
computed: {
needSendSunc: function() {
return this.sendSync;
}
},
created() {
this.$on("startCount", function() {
this.startCountFn();
});
this.$on("stopCount", function() {
this.stopCountFn();
});
},
methods: {
counterFn: function(counterTime) {
var vm = this;
var nowDate = new Date().getTime();
if (vm.pauseTime == 0) {
var num = counterTime - nowDate; //计算时间毫秒差
} else {
vm.pauseTime = vm.pauseTime + 10;
var num = counterTime - vm.pauseTime; //计算时间毫秒差
}
// console.log(num)
var leave1 = num % (24 * 3600 * 1000); //计算天数后剩余的毫秒数
var leave2 = leave1 % (3600 * 1000); //计算小时数后剩余的毫秒数
var leave3 = leave2 % (60 * 1000); //计算分钟数后剩余的毫秒数
vm.day = Math.floor(num / (24 * 3600 * 1000)); //计算相差天数
vm.hour = Math.floor(leave1 / (3600 * 1000)); //计算相差小时
vm.minute = Math.floor(leave2 / (60 * 1000)); //计算相差分钟
vm.second = Math.round(leave3 / 1000); //计算相差秒
if (vm.day > 0) {
vm.countString = `${vm.day}${vm.hour}小时 ${vm.minute}${
vm.second
}秒`;
} else if (vm.hour > 0) {
vm.countString = `${vm.hour}小时 ${vm.minute}${vm.second}秒`;
} else if (vm.minute > 0) {
vm.countString = `${vm.minute}${vm.second}秒`;
} else {
vm.countString = `${vm.second}秒`;
}
},
startCountFn: function() {
var vm = this;
if (!vm.isStart) {
vm.countVal = vm.countVal ? vm.countVal : new Date().getTime();
var timer = setInterval(function() {
vm.counterFn(vm.countVal);
}, 10);
vm.globalTimer = timer;
vm.isStart = true;
}
},
stopCountFn: function() {
var vm = this;
if (vm.isStart) {
window.clearInterval(vm.globalTimer);
vm.globalTimer = null;
vm.isStart = false;
vm.pauseTime = new Date().getTime();
}
},
passToParent: function(data) {
var vm = this;
this.$emit("getDataFromChild", data);
}
},
destroyed() {
this.stopCountFn();
}
};
</script>
<!--
安装 tinymce
$ npm install @tinymce/tinymce-vue -S
npm install tinymce -S
安装之后,在 node_modules 中找到 tinymce/skins 目录,然后将 skins 目录拷贝到 static 目录下
文中所有 static 目录相关都这样处理
tinymce 默认是英文界面,所以还需要下载一个中文语言包:
中文:https://tinymce-services.azurewebsites.net/1/i18n/download?langs=zh_CN
然后将这个语言包放到 static 目录下,为了结构清晰,包一层 tinymce 目录
static
|____tinymce
|___skins
|___zh_CN.js
<tinymce-edit ref="tinymceWrap" :bodyHtml="ruleForm.gradeDescribe" :projectName="projectName"></tinymce-edit>
import tinymceEdit from 'components/memberShip/tinymce-edit'
公共上传图片请求方法
/*
* method: 'post'
* data: params
*
*/
export const postForm = (url, params) => {
params.requestProject = "gic-clique";
return Vue.axios({
method: 'post',
url: `${local}${url}`,
data: params,
headers: {}
});
}
引入路径修改成自己的路径
-->
<template>
<div class="tinymce-contain">
<editor id='tinymce' v-model='tinymceHtml' :init='init'></editor>
</div>
</template>
<script>
// import { getRequest,postForm } from '../../api/api';
import tinymce from 'tinymce/tinymce'
import 'tinymce/themes/modern/theme'
import Editor from '@tinymce/tinymce-vue'
import 'tinymce/plugins/image'
import 'tinymce/plugins/link'
import 'tinymce/plugins/code'
import 'tinymce/plugins/table'
import 'tinymce/plugins/lists'
import 'tinymce/plugins/contextmenu'
import 'tinymce/plugins/wordcount'
import 'tinymce/plugins/colorpicker'
import 'tinymce/plugins/textcolor'
export default {
name: "tinymce-edit",
props: ["bodyHtml",'projectName'],
data() {
return {
// tinymce
tinymceHtml: '请输入内容',
init: {
language_url: 'static/tinymce/zh_CN.js',
language: 'zh_CN',
skin_url: 'static/tinymce/skins/lightgray',
height: 300,
// 图片上传
// without images_upload_url set, Upload tab won't show up
// images_upload_url: '',
// images_upload_base_path: '/some/basepath',
images_upload_credentials: true, //是否应传递cookie等跨域的凭据
// images_upload_handler提供三个参数:blobInfo, success, failure
images_upload_handler: (blobInfo, success, failure)=>{
console.log(blobInfo)
// this.handleImgUpload(blobInfo, success, failure)
},
// 添加插件
plugins: 'link lists image code table colorpicker textcolor wordcount contextmenu',
toolbar:
'bold italic underline strikethrough | fontsizeselect | forecolor backcolor | alignleft aligncenter alignright alignjustify | bullist numlist | outdent indent blockquote | undo redo | link unlink image code | removeformat',
branding: false,
setup: function(editor) {
// 点击编辑框回调
editor.on('click', function(e) {
console.log('Editor was clicked');
});
}
}
}
},
methods: {
// 上传图片
// handleImgUpload (blobInfo, success, failure) {
// var that = this
// let formdata = new FormData()
// formdata.set('upload_file', blobInfo.blob())
// formdata.set("requestProject",that.repProjectName);
// console.log(formdata)
// postForm('/api-plug/upload-img', formdata).then(res => {
// success(res.data.result[0].qcloudImageUrl)
// }).catch(res => {
// console.log(res)
// failure('error')
// })
// },
},
watch: {
projectName: function(newData,oldData){
var that = this;
// console.log("新数据:",newData,oldData)
that.repProjectName = newData || 'gic-web';
},
bodyHtml: function(newData,oldData){
var that = this;
// console.log("新数据:",newData,oldData)
that.tinymceHtml = newData;
},
},
components: {
Editor
},
mounted() {
var that = this
tinymce.init({});
that.tinymceHtml = that.bodyHtml;
}
}
</script>
<style lang="scss" scoped >
.tinymce-contain {
width: 890px;
/deep/ .mce-tinymce {
-webkit-box-shadow: none;
-moz-box-shadow: none;
box-shadow: none;
border: 1px solid #dcdfe6;
}
/deep/ .mce-panel {
border: 0px solid #dcdfe6;
}
}
</style>
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment