2 #2

Merged
p6hj2n987 merged 1 commits from yangyuxin_branch into main 1 year ago

@ -0,0 +1,114 @@
<template>
<div class="app-wrapper" :class="classObj">
<sidebar class="sidebar-container"></sidebar>
<div class="main-container">
<navbar></navbar>
<app-main></app-main>
</div>
</div>
</template>
<script>
import { Navbar, Sidebar, AppMain } from './components'
import ResizeMixin from './mixin/ResizeHandler'
export default {
name: 'layout',
components: {
Navbar,
Sidebar,
AppMain
},
mixins: [ResizeMixin],
computed: {
sidebar() {
return this.$store.state.app.sidebar
},
device() {
return this.$store.state.app.device
},
classObj() {
return {
hideSidebar: !this.sidebar.opened,
withoutAnimation: this.sidebar.withoutAnimation,
mobile: this.device === 'mobile'
}
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
@import "src/styles/mixin.scss";
.app-wrapper {
@include clearfix;
position: relative;
height: 100%;
width: 100%;
}
</style>
#abc
<template>
<!-- 最外层的 `div` 元素类名为 `app-wrapper`并且通过 `:class="classObj"` 进行动态类名绑定根据 `classObj` 计算属性返回的对象来决定应用哪些类名用于实现根据不同条件动态改变样式的功能 -->
<div class="app-wrapper" :class="classObj">
<!-- 使用名为 `sidebar` 的自定义组件类名为 `sidebar-container`通常这个组件用于展示页面的侧边栏内容比如导航菜单等功能模块 -->
<sidebar class="sidebar-container"></sidebar>
<!-- 一个 `div` 元素类名为 `main-container`作为主要内容区域的容器用于包裹页面中除侧边栏之外的核心展示内容 -->
<div class="main-container">
<!-- 使用名为 `navbar` 的自定义组件用于展示页面顶部的导航栏包含诸如页面切换功能操作入口等元素 -->
<navbar></navbar>
<!-- 使用名为 `app-main` 的自定义组件用于承载页面的主体内容例如数据展示操作表单等具体业务相关的内容展示 -->
<app-main></app-main>
</div>
</div>
</template>
<script>
// `./components` `Navbar``Sidebar``AppMain`
import { Navbar, Sidebar, AppMain } from './components';
// `./mixin/ResizeHandler` `ResizeMixin``mixins` Vue.js `ResizeMixin` 使
import ResizeMixin from './mixin/ResizeHandler';
export default {
name: 'layout',
components: {
// `components` `Navbar``Sidebar``AppMain` 使 Vue.js 使
Navbar,
Sidebar,
AppMain
},
mixins: [ResizeMixin],
computed: {
sidebar() {
// `sidebar`访 Vuex `$store` `app` `sidebar` 使
return this.$store.state.app.sidebar;
},
device() {
// `device` `$store` `app` `mobile`便
return this.$store.state.app.device;
},
classObj() {
// `classObj` `hideSidebar` `!this.sidebar.opened``withoutAnimation` `this.sidebar.withoutAnimation``mobile` `this.device === 'mobile'`
return {
hideSidebar:!this.sidebar.opened,
withoutAnimation: this.sidebar.withoutAnimation,
mobile: this.device ==='mobile'
};
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
// 使 `@import` `src/styles/mixin.scss` SCSS `mixin`便
@import "src/styles/mixin.scss";
.app-wrapper {
// 使 `clearfix` `mixin.scss` `clearfix`
@include clearfix;
// `relative`便
position: relative;
// `100%`
height: 100%;
// `100%`使
width: 100%;
}
</style>

@ -0,0 +1,43 @@
<template>
<section class="app-main">
<transition name="fade" mode="out-in">
<!-- <router-view :key="key"></router-view> -->
<router-view></router-view>
</transition>
</section>
</template>
<script>
export default {
name: 'AppMain',
computed: {
// key() {
// return this.$route.name !== undefined ? this.$route.name + +new Date() : this.$route + +new Date()
// }
}
}
</script>
#abc
<template>
<!-- 使用 `section` 元素作为容器类名为 `app-main`通常这个元素会用于承载页面的主体内容部分在整个页面布局结构中扮演重要角色例如展示各种业务模块数据展示区域等 -->
<section class="app-main">
<!-- 使用 Vue.js `<transition>` 组件设置过渡效果的名称为 `fade`过渡模式为 `out-in`这种过渡模式意味着旧元素先执行离开过渡淡出完成后新元素再执行进入过渡淡入用于在路由视图切换时不同页面组件切换时实现平滑的过渡动画效果 -->
<transition name="fade" mode="out-in">
<!-- 原本此处使用 `:key` 绑定了一个计算属性 `key`目前代码中该计算属性被注释掉了用于给 `<router-view>` 组件添加一个唯一标识当路由变化且 `key` 值改变时会强制重新渲染 `<router-view>` 对应的组件实现更有效的组件更新和过渡效果展示现在虽然没有绑定 `key` `<router-view>` 组件依然用于渲染与当前路由匹配的组件内容也就是展示具体的页面内容部分 -->
<router-view></router-view>
</transition>
</section>
</template>
<script>
export default {
name: 'AppMain',
computed: {
// `key` `<router-view>` `key`
// `this.$route.name` `key` `$route.name` `key` `$route.name` `+new Date()` `key` `<router-view>` `$route.name` `$route` `key` `<transition>`
// key() {
// return this.$route.name!== undefined? this.$route.name + +new Date() : this.$route + +new Date()
// }
}
}
</script>

@ -0,0 +1,235 @@
<template>
<el-menu class="navbar" mode="horizontal">
<hamburger class="hamburger-container" :toggleClick="toggleSideBar" :isActive="sidebar.opened"></hamburger>
<breadcrumb></breadcrumb>
<el-dropdown class="avatar-container" trigger="click">
<div class="avatar-wrapper">
<img class="user-avatar" :src="avatar">
<i class="el-icon-caret-bottom"></i>
</div>
<el-dropdown-menu class="user-dropdown" slot="dropdown">
<router-link class="inlineBlock" to="/">
<el-dropdown-item>
首页
</el-dropdown-item>
</router-link>
<el-dropdown-item divided>
<span @click="logout" style="display:block;">退出</span>
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</el-menu>
</template>
<script>
import { mapGetters } from 'vuex'
import Breadcrumb from '@/components/Breadcrumb'
import Hamburger from '@/components/Hamburger'
export default {
components: {
Breadcrumb,
Hamburger
},
computed: {
...mapGetters([
'sidebar',
'avatar'
])
},
methods: {
toggleSideBar() {
this.$store.dispatch('ToggleSideBar')
},
logout() {
this.$store.dispatch('LogOut').then(() => {
location.reload() // vue-router bug
})
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
.navbar {
height: 50px;
line-height: 50px;
border-radius: 0px !important;
.hamburger-container {
line-height: 58px;
height: 50px;
float: left;
padding: 0 10px;
}
.screenfull {
position: absolute;
right: 90px;
top: 16px;
color: red;
}
.avatar-container {
height: 50px;
display: inline-block;
position: absolute;
right: 35px;
.avatar-wrapper {
cursor: pointer;
margin-top: 5px;
position: relative;
.user-avatar {
width: 40px;
height: 40px;
border-radius: 10px;
}
.el-icon-caret-bottom {
position: absolute;
right: -20px;
top: 25px;
font-size: 12px;
}
}
}
}
</style>
#abc
<template>
<!-- 使用 Element UI `el-menu` 组件创建一个水平模式`mode="horizontal"`的导航栏类名为 `navbar`用于构建页面顶部的导航菜单结构 -->
<el-menu class="navbar" mode="horizontal">
<!-- 使用名为 `hamburger` 的自定义组件类名为 `hamburger-container`通过属性绑定传递 `toggleClick` 方法用于切换侧边栏显示状态以及 `isActive` 属性与侧边栏是否打开状态相关通常这个组件会以汉堡菜单图标形式呈现点击可展开或收起侧边栏 -->
<hamburger class="hamburger-container" :toggleClick="toggleSideBar" :isActive="sidebar.opened"></hamburger>
<!-- 使用名为 `breadcrumb` 的自定义组件用于展示面包屑导航显示当前页面在整个页面层级结构中的位置路径信息方便用户知晓所在页面的层级关系 -->
<breadcrumb></breadcrumb>
<!-- 使用 Element UI `el-dropdown` 下拉菜单组件类名为 `avatar-container`设置触发下拉菜单的方式为点击`trigger="click"`用于创建一个包含用户头像等相关操作选项的下拉菜单 -->
<el-dropdown class="avatar-container" trigger="click">
<!-- 一个 `div` 元素类名为 `avatar-wrapper`作为用户头像及其相关元素的容器用于包裹头像图片和下拉箭头图标等 -->
<div class="avatar-wrapper">
<!-- 使用 `img` 图片元素通过 `:src` 绑定 `avatar` 变量在计算属性中通过 Vuex `getters` 获取用于显示用户头像图片资源设置类名为 `user-avatar`用于展示用户的头像 -->
<img class="user-avatar" :src="avatar">
<!-- 使用 Element UI 的图标组件 `el-icon-caret-bottom`展示一个向下的箭头图标用于提示用户该区域可点击展开下拉菜单增强视觉提示效果 -->
<i class="el-icon-caret-bottom"></i>
</div>
<!-- `el-dropdown-menu` 组件类名为 `user-dropdown`通过 `slot="dropdown"` 插槽与外层的 `el-dropdown` 组件关联用于定义下拉菜单中具体的菜单项内容 -->
<el-dropdown-menu class="user-dropdown" slot="dropdown">
<!-- 使用 `router-link` 组件创建一个路由链接类名为 `inlineBlock`链接到根路径`/`点击可导航到首页并且将 `el-dropdown-item` 作为子元素包裹在其中用于在下拉菜单中展示首页这一菜单项 -->
<router-link class="inlineBlock" to="/">
<el-dropdown-item>
首页
</el-dropdown-item>
</router-link>
<!-- `el-dropdown-item` 组件设置了 `divided` 属性可能用于添加分割线样式区分不同菜单项内部包含一个 `span` 元素点击该 `span` 元素会触发 `logout` 方法用于执行退出登录的操作在下拉菜单中展示退出这一菜单项 -->
<el-dropdown-item divided>
<span @click="logout" style="display:block;">退出</span>
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</el-menu>
</template>
<script>
// `vuex` `mapGetters` Vuex `getters` 便 Vuex
import { mapGetters } from 'vuex';
// `@/components/` `Breadcrumb`
import Breadcrumb from '@/components/Breadcrumb';
// `@/components/` `Hamburger`
import Hamburger from '@/components/Hamburger';
export default {
components: {
// `components` `Breadcrumb` `Hamburger` 使 Vue.js 使
Breadcrumb,
Hamburger
},
computed: {
// 使 `mapGetters` Vuex `sidebar` `avatar` `getters` `this.sidebar` `this.avatar` `sidebar` `avatar`
...mapGetters([
'sidebar',
'avatar'
])
},
methods: {
toggleSideBar() {
// `toggleSideBar` `$store.dispatch` Vuex `ToggleSideBar` `action` Vuex `store`
this.$store.dispatch('ToggleSideBar')
},
logout() {
// `logout` 退 `$store.dispatch` Vuex `LogOut` `store` `LogOut` `.then` `location.reload` `vue-router` `bug`退
this.$store.dispatch('LogOut').then(() => {
location.reload(); // vue-router bug
})
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
// `navbar`
.navbar {
// `50px`使
height: 50px;
// `50px`使
line-height: 50px;
// `el-menu` `0px`使`!important`
border-radius: 0px!important;
// `hamburger-container`
.hamburger-container {
// `58px`使
line-height: 58px;
// `50px`
height: 50px;
// 使便
float: left;
// `0 10px`
padding: 0 10px;
}
// `screenfull` 使
.screenfull {
// `absolute`使便
position: absolute;
// `90px` 使
right: 90px;
// `16px`
top: 16px;
//
color: red;
}
// `avatar-container`
.avatar-container {
// `50px`使
height: 50px;
// `inline-block`使
display: inline-block;
// `absolute`便便
position: absolute;
// `35px` 使
right: 35px;
// `avatar-wrapper`
.avatar-wrapper {
// `cursor: pointer`
cursor: pointer;
// `5px`使
margin-top: 5px;
// `relative`便
position: relative;
// `user-avatar`
.user-avatar {
// `40px`使
width: 40px;
// `40px`
height: 40px;
// `10px`使
border-radius: 10px;
}
// `el-icon-caret-bottom`
.el-icon-caret-bottom {
// `absolute`使 `avatar-wrapper`
position: absolute;
// `-20px` 使
right: -20px;
// `25px` 使
top: 25px;
// `12px`使
font-size: 12px;
}
}
}
}
</style>

@ -0,0 +1,154 @@
<template>
<div class="menu-wrapper">
<template v-for="item in routes" v-if="!item.hidden&&item.children">
<router-link v-if="hasOneShowingChildren(item.children) && !item.children[0].children&&!item.alwaysShow" :to="item.path+'/'+item.children[0].path"
:key="item.children[0].name">
<el-menu-item :index="item.path+'/'+item.children[0].path" :class="{'submenu-title-noDropdown':!isNest}">
<svg-icon v-if="item.children[0].meta&&item.children[0].meta.icon" :icon-class="item.children[0].meta.icon"></svg-icon>
<span v-if="item.children[0].meta&&item.children[0].meta.title" slot="title">{{item.children[0].meta.title}}</span>
</el-menu-item>
</router-link>
<el-submenu v-else :index="item.name||item.path" :key="item.name">
<template slot="title">
<svg-icon v-if="item.meta&&item.meta.icon" :icon-class="item.meta.icon"></svg-icon>
<span v-if="item.meta&&item.meta.title" slot="title">{{item.meta.title}}</span>
</template>
<template v-for="child in item.children" v-if="!child.hidden">
<sidebar-item :is-nest="true" class="nest-menu" v-if="child.children&&child.children.length>0" :routes="[child]" :key="child.path"></sidebar-item>
<!--支持外链功能-->
<a v-else-if="child.path.startsWith('http')" v-bind:href="child.path" target="_blank" :key="child.name">
<el-menu-item :index="item.path+'/'+child.path">
<svg-icon v-if="child.meta&&child.meta.icon" :icon-class="child.meta.icon"></svg-icon>
<span v-if="child.meta&&child.meta.title" slot="title">{{child.meta.title}}</span>
</el-menu-item>
</a>
<router-link v-else :to="item.path+'/'+child.path" :key="child.name">
<el-menu-item :index="item.path+'/'+child.path">
<svg-icon v-if="child.meta&&child.meta.icon" :icon-class="child.meta.icon"></svg-icon>
<span v-if="child.meta&&child.meta.title" slot="title">{{child.meta.title}}</span>
</el-menu-item>
</router-link>
</template>
</el-submenu>
</template>
</div>
</template>
<script>
export default {
name: 'SidebarItem',
props: {
routes: {
type: Array
},
isNest: {
type: Boolean,
default: false
}
},
methods: {
hasOneShowingChildren(children) {
const showingChildren = children.filter(item => {
return !item.hidden
})
if (showingChildren.length === 1) {
return true
}
return false
}
}
}
</script>
#abc
<template>
<!-- 最外层的 `div` 元素类名为 `menu-wrapper`它充当整个侧边栏菜单的包裹容器起到组织和定位内部菜单元素的作用使得菜单在页面布局中有一个明确的范围和样式框架 -->
<div class="menu-wrapper">
<!-- 使用 `v-for` 指令对 `routes` 数组进行循环遍历`routes` 数组应该是从父组件传入的数据包含了侧边栏菜单各个层级的路由配置信息等内容同时结合 `v-if` 指令进行条件判断只有当当前遍历到的 `item` 元素的 `hidden` 属性为 `false`意味着该菜单项需要显示出来而非隐藏并且 `item` 具有 `children` 属性表示这个菜单项包含子菜单才会渲染内部对应的菜单结构内容以此实现根据数据动态生成侧边栏菜单的功能有选择性地展示有效的菜单项及其子菜单 -->
<template v-for="item in routes" v-if="!item.hidden&&item.children">
<!-- 使用 `router-link` 组件创建一个路由链接这是 Vue Router 用于实现页面导航的组件通过 `v-if` 指令添加了复杂的条件判断只有当满足以下几个条件时才会渲染这个路由链接
- `hasOneShowingChildren(item.children)` 方法返回 `true`即当前菜单项`item`的子菜单数组`item.children`经过筛选后只有一个需要显示的子项`hasOneShowingChildren` 方法会在后续的 `methods` 部分详细介绍作用是筛选出未隐藏的子项并判断数量是否为 1
- `!item.children[0].children` `true`也就是当前菜单项的第一个子菜单项`item.children[0]`不存在它自己的子菜单说明是一个没有下一级嵌套的子菜单项
- `!item.alwaysShow` `true`可能表示该菜单项不是那种始终要显示特定链接形式的特殊菜单项具体含义需结合整体业务逻辑判断
`:to` 属性绑定了一个拼接后的路由路径由当前菜单项`item` `path` 属性和它第一个子菜单项`item.children[0]` `path` 属性通过 `+` 号拼接而成用于指定点击这个路由链接后要跳转到的页面路径`:key` 属性绑定了第一个子菜单项`item.children[0]` `name` 属性 Vue 的虚拟 DOM 渲染机制中`key` 可以帮助更高效准确地识别每个列表项这里的路由链接作为一种列表项便于在数据变化时进行精准的更新复用以及重新排序等操作优化渲染性能 -->
<router-link v-if="hasOneShowingChildren(item.children) &&!item.children[0].children&&!item.alwaysShow" :to="item.path+'/'+item.children[0].path"
:key="item.children[0].name">
<!-- 使用 Element UI `el-menu-item` 组件来表示一个菜单项它是侧边栏菜单中的具体可点击选项`:index` 属性绑定了同样是由当前菜单项`item` `path` 和其第一个子菜单项`item.children[0]` `path` 拼接而成的字符串这个 `index` 属性在菜单的选中状态管理等方面可能会起到标识作用例如确定哪个菜单项当前处于激活状态等`:class` 属性通过对象语法绑定了一个类名控制逻辑根据 `!isNest``isNest` 是从父组件传入的布尔类型属性用于判断当前菜单项是否处于嵌套菜单的情境中默认值为 `false`的值来决定是否添加 `submenu-title-noDropdown` 类名从而实现根据不同的菜单层级或状态应用不同的样式比如改变外观样式以体现是否是嵌套菜单下的标题样式等 -->
<el-menu-item :index="item.path+'/'+item.children[0].path" :class="{'submenu-title-noDropdown':!isNest}">
<!-- 使用 `svg-icon` 自定义组件从代码上下文推测应该是用于展示 SVG 格式的图标通过 `v-if` 指令进行条件判断只有当当前菜单项的第一个子菜单项`item.children[0]` `meta` 对象中存在 `icon` 属性通常 `meta` 对象用于存放菜单项的一些额外元数据比如图标标题等相关信息才会将该 `icon` 属性的值作为图标类名传递给 `svg-icon` 组件进而展示对应的图标用于增强菜单项的可视化效果让用户更直观地识别菜单项对应的功能或页面类型等 -->
<svg-icon v-if="item.children[0].meta&&item.children[0].meta.icon" :icon-class="item.children[0].meta.icon"></svg-icon>
<!-- 使用 `span` 元素来展示菜单项的标题文本内容同样通过 `v-if` 指令判断只有当当前菜单项的第一个子菜单项`item.children[0]` `meta` 对象中存在 `title` 属性时才会将该 `title` 属性的值作为文本内容渲染到 `span` 元素中显示在菜单项上明确告知用户该菜单项所代表的含义或对应的页面名称等信息通过 `slot="title"` 属性将这个 `span` 元素放置在 `el-menu-item` 组件指定的标题插槽位置确保文本正确显示在菜单项的合适位置上 -->
<span v-if="item.children[0].meta&&item.children[0].meta.title" slot="title">{{item.children[0].meta.title}}</span>
</el-menu-item>
</router-link>
<!-- 使用 Element UI `el-submenu` 组件来创建一个包含子菜单的菜单项容器通过 `v-else` 指令意味着当不满足上面 `router-link` 组件的渲染条件时即当前菜单项的子菜单情况不符合前面所描述的那种只有一个无子菜单的显示子项的情况就会渲染这个 `el-submenu` 组件用于展示更复杂的具有多个子菜单项或者嵌套子菜单的菜单结构`:index` 属性绑定了当前菜单项`item` `name` 属性或者 `path` 属性优先使用 `name`如果 `name` 不存在则使用 `path`用于在菜单体系中标识这个子菜单的唯一性便于菜单状态管理展开与折叠控制等操作能准确对应到这个子菜单上`:key` 属性绑定了当前菜单项`item` `name` 属性同样是为了在 Vue 的虚拟 DOM 渲染机制中给这个组件实例一个唯一标识方便进行高效的更新和复用操作提升渲染性能 -->
<el-submenu v-else :index="item.name||item.path" :key="item.name">
<!-- 使用 `template` 元素并通过 `slot="title"` 属性定义了 `el-submenu` 组件的标题部分内容的插槽用于自定义子菜单标题的展示样式和内容包括图标和标题文本等信息这些内容会展示在子菜单展开按钮等位置方便用户直观地识别这个子菜单所代表的功能分类或相关页面集合等 -->
<template slot="title">
<!-- 与前面类似使用 `svg-icon` 自定义组件展示图标通过 `v-if` 指令判断当前菜单项`item` `meta` 对象中是否存在 `icon` 属性如果存在则将该 `icon` 属性的值作为图标类名传递给 `svg-icon` 组件展示对应的图标增强子菜单标题的可视化效果帮助用户快速识别该子菜单相关信息 -->
<svg-icon v-if="item.meta&&item.meta.icon" :icon-class="item.meta.icon"></svg-icon>
<!-- 使用 `span` 元素展示子菜单标题的文本内容通过 `v-if` 指令判断当前菜单项`item` `meta` 对象中是否存在 `title` 属性如果存在则将该 `title` 属性的值作为文本渲染到 `span` 元素中显示在子菜单标题位置明确告知用户这个子菜单所涉及的内容范围等信息通过 `slot="title"` 将这个 `span` 元素放置在 `template` 元素指定的标题插槽位置确保文本正确显示在子菜单标题的合适位置上 -->
<span v-if="item.meta&&item.meta.title" slot="title">{{item.meta.title}}</span>
</template>
<!-- 再次使用 `v-for` 指令对当前菜单项`el-submenu` 对应的 `item` `children` 数组即子菜单的各个子项进行循环遍历同时结合 `v-if` 指令进行条件判断只有当子项的 `hidden` 属性为 `false`表示该子项需要显示出来而非隐藏才会渲染内部对应的菜单相关组件用于动态生成子菜单下的具体菜单项内容实现有选择性地展示有效的子菜单项过滤掉不需要显示的子项 -->
<template v-for="child in item.children" v-if="!child.hidden">
<!-- 使用名为 `sidebar-item` 的自定义组件也就是当前正在定义的这个组件自身通过递归调用自身来处理嵌套菜单的情况通过 `:is-nest="true"` 属性传递一个布尔值表示当前处于嵌套菜单的状态因为这里是在处理子菜单中的子菜单项属于嵌套层级情况同时添加 `nest-menu` 类名从代码结构推测这个类名应该是用于应用一些特定的样式实现嵌套菜单相关的外观样式控制比如缩进字体大小调整等具体样式定义需查看对应的 CSS 代码通过 `:routes="[child]"` 属性将当前子项`child`作为一个只包含一个元素的数组传递给 `sidebar-item` 组件目的是让 `sidebar-item` 组件以这个子项为基础数据递归地处理它可能存在的子菜单情况如果 `child` 本身也有 `children` 属性即还有下一层级的子菜单的话`:key` 属性绑定当前子项`child` `path` 属性用于在 Vue 的虚拟 DOM 渲染机制中给这个 `sidebar-item` 组件实例一个唯一标识便于进行高效的更新复用以及重新排序等操作优化渲染性能实现多级嵌套菜单的递归构建和渲染功能 -->
<sidebar-item :is-nest="true" class="nest-menu" v-if="child.children&&child.children.length>0" :routes="[child]" :key="child.path"></sidebar-item>
<!-- 使用 `a` 元素超链接来创建一个外部链接菜单项通过 `v-else-if` 指令进行条件判断当当前子项`child` `path` 属性是以 `http` 开头时表明这是一个指向外部网页的链接就会渲染这个 `a` 元素通过 `v-bind:href` 指令将当前子项`child` `path` 属性绑定为超链接的 `href` 属性值即链接地址同时设置 `target="_blank"` 属性使得点击这个链接时会在新的浏览器标签页中打开对应的外部网页`:key` 属性绑定当前子项`child` `name` 属性用于在 Vue 的虚拟 DOM 渲染机制中给这个 `a` 元素一个唯一标识方便进行高效的更新操作确保在数据变化时能正确渲染这个外部链接菜单项 -->
<a v-else-if="child.path.startsWith('http')" v-bind:href="child.path" target="_blank" :key="child.name">
<el-menu-item :index="item.path+'/'+child.path">
<svg-icon v-if="child.meta&&child.meta.icon" :icon-class="child.meta.icon"></svg-icon>
<span v-if="child.meta&&child.meta.title" slot="title">{{child.meta.title}}</span>
</el-menu-item>
</a>
<!-- 使用 `router-link` 组件创建一个内部路由链接菜单项通过 `v-else` 指令当不满足上面 `sidebar-item` `a` 元素的渲染条件时即既不是嵌套菜单情况也不是外部链接情况而是内部页面路由链接的常规情况就会渲染这个 `router-link` 组件`:to` 属性绑定了由当前菜单项`el-submenu` 对应的 `item` `path` 属性和当前子项`child` `path` 属性通过 `+` 号拼接而成的字符串作为路由路径用于指定点击这个路由链接后要跳转到的内部页面路径`:key` 属性绑定当前子项`child` `name` 属性用于在 Vue 的虚拟 DOM 渲染机制中给这个 `router-link` 组件一个唯一标识方便进行高效的更新操作确保在数据变化时能正确渲染这个内部路由链接菜单项实现内部页面之间的导航功能 -->
<router-link v-else :to="item.path+'/'+child.path" :key="child.name">
<el-menu-item :index="item.path+'/'+child.path">
<svg-icon v-if="child.meta&&child.meta.icon" :icon-class="child.meta.icon"></svg-icon>
<span v-if="child.meta&&child.meta.title" slot="title">{{child.meta.title}}</span>
</el-menu-item>
</router-link>
</template>
</el-submenu>
</template>
</div>
</template>
<script>
export default {
name: 'SidebarItem',
props: {
// `routes` `Array` `path``meta``children`
routes: {
type: Array
},
// `isNest` `Boolean` `false`
isNest: {
type: Boolean,
default: false
}
},
methods: {
hasOneShowingChildren(children) {
// `hasOneShowingChildren` `children` `item.children`
const showingChildren = children.filter(item => {
// 使 JavaScript `filter` `children` `filter` `true` `hidden` `false` `showingChildren`
return!item.hidden
})
if (showingChildren.length === 1) {
// `showingChildren` `1` `1` `true`
return true
}
return false
// `showingChildren` `1` `false`使
}
}
}
</script>

@ -0,0 +1,89 @@
<template>
<scroll-bar>
<el-menu
mode="vertical"
:show-timeout="200"
:default-active="$route.path"
:collapse="isCollapse"
background-color="#304156"
text-color="#bfcbd9"
active-text-color="#409EFF"
>
<sidebar-item :routes="routes"></sidebar-item>
</el-menu>
</scroll-bar>
</template>
<script>
import { mapGetters } from 'vuex'
import SidebarItem from './SidebarItem'
import ScrollBar from '@/components/ScrollBar'
export default {
components: { SidebarItem, ScrollBar },
computed: {
...mapGetters([
'sidebar',
'routers'
]),
routes() {
// return this.$router.options.routes
return this.routers
},
isCollapse() {
return !this.sidebar.opened
}
}
}
</script>
#abc
<template>
<!-- 使用名为 `scroll-bar` 的自定义组件可能是用于实现滚动条相关功能的组件比如自定义滚动条样式添加滚动交互逻辑等该组件作为外层容器包裹内部的 `el-menu` 组件以便为菜单提供滚动功能如果有相应实现的话 -->
<scroll-bar>
<!-- 使用 Element UI `el-menu` 菜单组件设置菜单模式为 `vertical`垂直方向展示`:show-timeout` 属性设置为 `200`表示菜单展开或收起时过渡动画的显示时长单位可能是毫秒`:default-active` 属性绑定为 `$route.path`意味着会根据当前路由的路径来自动设置默认激活的菜单项与当前页面匹配的菜单项处于激活状态`:collapse` 属性绑定 `isCollapse` 变量在计算属性中定义与侧边栏是否折叠相关用于控制菜单是否折叠显示同时设置了背景颜色文本颜色以及激活状态下的文本颜色用于定义菜单的整体外观颜色风格 -->
<el-menu
mode="vertical"
:show-timeout="200"
:default-active="$route.path"
:collapse="isCollapse"
background-color="#304156"
text-color="#bfcbd9"
active-text-color="#409EFF"
>
<!-- 使用名为 `sidebar-item` 的自定义组件 `script` 部分有导入和注册用于根据传入的路由配置信息动态生成侧边栏菜单的具体内容通过 `:routes` 属性传递 `routes` 变量在计算属性中定义包含了菜单相关的路由配置数据用于递归地构建完整的侧边栏菜单结构展示各个菜单项及其嵌套子菜单等内容 -->
<sidebar-item :routes="routes"></sidebar-item>
</el-menu>
</scroll-bar>
</template>
<script>
// `vuex` `mapGetters` Vuex `getters` 便 Vuex 便使
import { mapGetters } from 'vuex';
// `SidebarItem`
import SidebarItem from './SidebarItem';
// `@/components/` `ScrollBar` `el-menu`
import ScrollBar from '@/components/ScrollBar';
export default {
components: {
// `components` `SidebarItem` `ScrollBar` 使 Vue.js 使
SidebarItem,
ScrollBar
},
computed: {
// 使 `mapGetters` Vuex `sidebar` `routers` `getters` `this.sidebar` `this.routers` `sidebar` `routers`
...mapGetters([
'sidebar',
'routers'
]),
routes() {
// `routes` `return this.$router.options.routes` `this.routers` `mapGetters` Vuex `sidebar-item`
return this.routers;
},
isCollapse() {
// `isCollapse` `this.sidebar.opened` `sidebar.opened` `false` `isCollapse` `true` `false` `el-menu` `:collapse`
return!this.sidebar.opened;
}
}
}
</script>

@ -0,0 +1,3 @@
export { default as Navbar } from './Navbar'
export { default as Sidebar } from './Sidebar'
export { default as AppMain } from './AppMain'

@ -0,0 +1,41 @@
import store from '@/store'
const { body } = document
const WIDTH = 1024
const RATIO = 3
export default {
watch: {
$route(route) {
if (this.device === 'mobile' && this.sidebar.opened) {
store.dispatch('CloseSideBar', { withoutAnimation: false })
}
}
},
beforeMount() {
window.addEventListener('resize', this.resizeHandler)
},
mounted() {
const isMobile = this.isMobile()
if (isMobile) {
store.dispatch('ToggleDevice', 'mobile')
store.dispatch('CloseSideBar', { withoutAnimation: true })
}
},
methods: {
isMobile() {
const rect = body.getBoundingClientRect()
return rect.width - RATIO < WIDTH
},
resizeHandler() {
if (!document.hidden) {
const isMobile = this.isMobile()
store.dispatch('ToggleDevice', isMobile ? 'mobile' : 'desktop')
if (isMobile) {
store.dispatch('CloseSideBar', { withoutAnimation: true })
}
}
}
}
}

@ -0,0 +1,386 @@
<template>
<div>
<el-card class="login-form-layout">
<el-form autoComplete="on"
:model="loginForm"
:rules="loginRules"
ref="loginForm"
label-position="left">
<div style="text-align: center">
<svg-icon icon-class="login-mall" style="width: 56px;height: 56px;color: #409EFF"></svg-icon>
</div>
<h2 class="login-title color-main">mall-admin-web</h2>
<el-form-item prop="username">
<el-input name="username"
type="text"
v-model="loginForm.username"
autoComplete="on"
placeholder="请输入用户名">
<span slot="prefix">
<svg-icon icon-class="user" class="color-main"></svg-icon>
</span>
</el-input>
</el-form-item>
<el-form-item prop="password">
<el-input name="password"
:type="pwdType"
@keyup.enter.native="handleLogin"
v-model="loginForm.password"
autoComplete="on"
placeholder="请输入密码">
<span slot="prefix">
<svg-icon icon-class="password" class="color-main"></svg-icon>
</span>
<span slot="suffix" @click="showPwd">
<svg-icon icon-class="eye" class="color-main"></svg-icon>
</span>
</el-input>
</el-form-item>
<el-form-item style="margin-bottom: 60px;text-align: center">
<el-button style="width: 45%" type="primary" :loading="loading" @click.native.prevent="handleLogin">
登录
</el-button>
<el-button style="width: 45%" type="primary" @click.native.prevent="handleTry">
获取体验账号
</el-button>
</el-form-item>
</el-form>
</el-card>
<img :src="login_center_bg" class="login-center-layout">
<el-dialog
title="公众号二维码"
:visible.sync="dialogVisible"
:show-close="false"
:center="true"
width="30%">
<div style="text-align: center">
<span class="font-title-large"><span class="color-main font-extra-large">关注公众号</span>回复<span class="color-main font-extra-large">体验</span>获取体验账号</span>
<br>
<img src="http://macro-oss.oss-cn-shenzhen.aliyuncs.com/mall/banner/qrcode_for_macrozheng_258.jpg" width="160" height="160" style="margin-top: 10px">
</div>
<span slot="footer" class="dialog-footer">
<el-button type="primary" @click="dialogConfirm"></el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import {isvalidUsername} from '@/utils/validate';
import {setSupport,getSupport,setCookie,getCookie} from '@/utils/support';
import login_center_bg from '@/assets/images/login_center_bg.png'
export default {
name: 'login',
data() {
const validateUsername = (rule, value, callback) => {
if (!isvalidUsername(value)) {
callback(new Error('请输入正确的用户名'))
} else {
callback()
}
};
const validatePass = (rule, value, callback) => {
if (value.length < 3) {
callback(new Error('密码不能小于3位'))
} else {
callback()
}
};
return {
loginForm: {
username: '',
password: '',
},
loginRules: {
username: [{required: true, trigger: 'blur', validator: validateUsername}],
password: [{required: true, trigger: 'blur', validator: validatePass}]
},
loading: false,
pwdType: 'password',
login_center_bg,
dialogVisible:false,
supportDialogVisible:false
}
},
created() {
this.loginForm.username = getCookie("username");
this.loginForm.password = getCookie("password");
if(this.loginForm.username === undefined||this.loginForm.username==null||this.loginForm.username===''){
this.loginForm.username = 'admin';
}
if(this.loginForm.password === undefined||this.loginForm.password==null){
this.loginForm.password = '';
}
},
methods: {
showPwd() {
if (this.pwdType === 'password') {
this.pwdType = ''
} else {
this.pwdType = 'password'
}
},
handleLogin() {
this.$refs.loginForm.validate(valid => {
if (valid) {
// let isSupport = getSupport();
// if(isSupport===undefined||isSupport==null){
// this.dialogVisible =true;
// return;
// }
this.loading = true;
this.$store.dispatch('Login', this.loginForm).then(() => {
this.loading = false;
setCookie("username",this.loginForm.username,15);
setCookie("password",this.loginForm.password,15);
this.$router.push({path: '/'})
}).catch(() => {
this.loading = false
})
} else {
console.log('参数验证不合法!');
return false
}
})
},
handleTry(){
this.dialogVisible =true
},
dialogConfirm(){
this.dialogVisible =false;
setSupport(true);
},
dialogCancel(){
this.dialogVisible = false;
setSupport(false);
}
}
}
</script>
<style scoped>
.login-form-layout {
position: absolute;
left: 0;
right: 0;
width: 360px;
margin: 140px auto;
border-top: 10px solid #409EFF;
}
.login-title {
text-align: center;
}
.login-center-layout {
background: #409EFF;
width: auto;
height: auto;
max-width: 100%;
max-height: 100%;
margin-top: 200px;
}
</style>
#abc
<template>
<div>
<!-- 使用 Element UI `el-card` 组件创建一个卡片式的布局容器类名为 `login-form-layout`用于包裹登录相关的表单元素使其在页面上呈现出特定的视觉效果和布局样式 -->
<el-card class="login-form-layout">
<!-- 使用 Element UI `el-form` 表单组件设置自动补全功能为 `on`绑定数据模型为 `loginForm`关联表单验证规则为 `loginRules`设置表单引用名为 `loginForm`标签位置为 `left`通常指表单标签在输入框左侧显示用于构建登录表单 -->
<el-form autoComplete="on"
:model="loginForm"
:rules="loginRules"
ref="loginForm"
label-position="left">
<!-- 一个 `div` 元素设置文本居中对齐内部使用 `svg-icon` 组件展示一个名为 `login-mall` 的图标用于在登录表单上方显示一个具有特定样式宽度高度颜色的图标可能起到品牌标识等作用 -->
<div style="text-align: center">
<svg-icon icon-class="login-mall" style="width: 56px;height: 56px;color: #409EFF"></svg-icon>
</div>
<!-- 一个 `h2` 标题元素类名为 `login-title` `color-main`用于显示页面标题mall-admin-web通过 `color-main` 类可能会应用特定的主色调样式 -->
<h2 class="login-title color-main">mall-admin-web</h2>
<!-- `el-form-item` 表单元素指定了验证属性 `prop` `username`用于包裹用户名输入框相关的内容对应表单中用户名的输入和验证部分 -->
<el-form-item prop="username">
<!-- 使用 Element UI `el-input` 输入框组件设置 `name` 属性为 `username`类型为 `text`双向绑定 `loginForm.username` 数据自动补全功能为 `on`设置占位提示文字为请输入用户名用于接收用户输入的用户名 -->
<el-input name="username"
type="text"
v-model="loginForm.username"
autoComplete="on"
placeholder="请输入用户名">
<!-- 使用 `slot` 插槽在输入框前缀位置插入一个 `svg-icon` 组件图标类名为 `user` 且应用 `color-main` 用于在用户名输入框前面展示一个表示用户的图标增强视觉提示效果 -->
<span slot="prefix">
<svg-icon icon-class="user" class="color-main"></svg-icon>
</span>
</el-input>
</el-form-item>
<!-- `el-form-item` 表单元素指定验证属性 `prop` `password`用于包裹密码输入框相关的内容对应表单中密码的输入和验证部分 -->
<el-form-item prop="password">
<!-- 使用 Element UI `el-input` 输入框组件设置 `name` 属性为 `password`类型通过 `pwdType` 变量动态控制用于实现密码显示隐藏切换功能监听回车键按下事件`@keyup.enter.native`并触发 `handleLogin` 方法双向绑定 `loginForm.password` 数据自动补全功能为 `on`设置占位提示文字为请输入密码用于接收用户输入的密码 -->
<el-input name="password"
:type="pwdType"
@keyup.enter.native="handleLogin"
v-model="loginForm.password"
autoComplete="on"
placeholder="请输入密码">
<!-- 使用 `slot` 插槽在输入框前缀位置插入一个 `svg-icon` 组件图标类名为 `password` 且应用 `color-main` 用于在密码输入框前面展示一个表示密码的图标增强视觉提示效果 -->
<span slot="prefix">
<svg-icon icon-class="password" class="color-main"></svg-icon>
</span>
<!-- 使用 `slot` 插槽在输入框后缀位置插入一个 `svg-icon` 组件图标类名为 `eye` 且应用 `color-main` 点击该图标可触发 `showPwd` 方法用于实现密码显示隐藏的切换功能通过点击眼睛图标来切换密码的可见性 -->
<span slot="suffix" @click="showPwd">
<svg-icon icon-class="eye" class="color-main"></svg-icon>
</span>
</el-input>
</el-form-item>
<!-- `el-form-item` 表单元素设置了底部外边距为 `60px` 且文本居中对齐用于放置登录和获取体验账号的按钮作为表单底部的操作按钮区域 -->
<el-form-item style="margin-bottom: 60px;text-align: center">
<!-- 使用 Element UI `el-button` 按钮组件设置样式宽度为 `45%`按钮类型为 `primary`通常表示主要的突出显示的按钮样式通过 `loading` 变量控制加载状态显示如按钮显示加载动画等点击时触发 `handleLogin` 方法阻止默认行为防止表单提交等默认操作用于执行登录操作 -->
<el-button style="width: 45%" type="primary" :loading="loading" @click.native.prevent="handleLogin">
登录
</el-button>
<!-- 使用 Element UI `el-button` 按钮组件设置样式宽度为 `45%`按钮类型为 `primary`点击时触发 `handleTry` 方法阻止默认行为用于执行获取体验账号的操作 -->
<el-button style="width: 45%" type="primary" @click.native.prevent="handleTry">
获取体验账号
</el-button>
</el-form-item>
</el-form>
</el-card>
<!-- 使用 `img` 图片元素通过 `:src` 绑定 `login_center_bg` 变量 `data` 中定义应该是图片资源路径类名为 `login-center-layout`用于展示登录页面中心位置的背景图片根据样式设置其布局和显示效果 -->
<img :src="login_center_bg" class="login-center-layout">
<!-- 使用 Element UI `el-dialog` 对话框组件设置标题为公众号二维码通过双向绑定控制对话框的显示隐藏状态`:visible.sync`设置不显示关闭按钮`:show-close="false"`使其居中显示`:center="true"`宽度为 `30%`用于弹出展示公众号二维码相关信息的对话框 -->
<el-dialog
title="公众号二维码"
:visible.sync="dialogVisible"
:show-close="false"
:center="true"
width="30%">
<!-- 一个 `div` 元素设置文本居中对齐内部包含文本和图片元素用于在对话框中展示引导用户关注公众号获取体验账号的相关提示信息以及公众号二维码图片 -->
<div style="text-align: center">
<span class="font-title-large"><span class="color-main font-extra-large">关注公众号</span>回复<span class="color-main font-extra-large">体验</span>获取体验账号</span>
<br>
<!-- 使用 `img` 图片元素设置图片源地址宽度高度以及顶部外边距用于展示公众号二维码图片 -->
<img src="http://macro-oss.oss-cn-shenzhen.aliyuncs.com/mall/banner/qrcode_for_macrozheng_258.jpg" width="160" height="160" style="margin-top: 10px">
</div>
<span slot="footer" class="dialog-footer">
<!-- 使用 Element UI `el-button` 按钮组件按钮类型为 `primary`点击时触发 `dialogConfirm` 方法用于在对话框中执行确定操作 -->
<el-button type="primary" @click="dialogConfirm"></el-button>
</span>
</el-dialog>
</div>
</template>
<script>
// `@/utils/validate` `isvalidUsername`
import {isvalidUsername} from '@/utils/validate';
// `@/utils/support` `setSupport` `getSupport` `setCookie` `getCookie` `Cookie`
import {setSupport,getSupport,setCookie,getCookie} from '@/utils/support';
// `login_center_bg` `@/assets/images/login_center_bg.png`
import login_center_bg from '@/assets/images/login_center_bg.png'
export default {
name: 'login',
data() {
const validateUsername = (rule, value, callback) => {
// `isvalidUsername` `Error`
if (!isvalidUsername(value)) {
callback(new Error('请输入正确的用户名'))
} else {
callback()
}
};
const validatePass = (rule, value, callback) => {
// `3` `Error` 3
if (value.length < 3) {
callback(new Error('密码不能小于3位'))
} else {
callback()
}
};
return {
// `username` `password` `Cookie`
loginForm: {
username: '',
password: '',
},
// `username` `required: true``trigger: 'blur'`使 `validateUsername` `password` 使 `validatePass`
loginRules: {
username: [{required: true, trigger: 'blur', validator: validateUsername}],
password: [{required: true, trigger: 'blur', validator: validatePass}]
},
// `false` `true` `false`
loading: false,
// `password` `showPwd` `password`
pwdType: 'password',
//
login_center_bg,
// `false` `true`
dialogVisible:false,
// 使 `false`
supportDialogVisible:false
}
},
created() {
// `Cookie` `loginForm.username` `loginForm.password` `null` `admin` `null` `Cookie`
this.loginForm.username = getCookie("username");
this.loginForm.password = getCookie("password");
if(this.loginForm.username === undefined||this.loginForm.username==null||this.loginForm.username===''){
this.loginForm.username = 'admin';
}
if(this.loginForm.password === undefined||this.loginForm.password==null){
this.loginForm.password = '';
}
},
methods: {
showPwd() {
// `pwdType` `password` 使 `pwdType` `password`
if (this.pwdType === 'password') {
this.pwdType = ''
} else {
this.pwdType = 'password'
}
},
handleLogin() {
// `$refs.loginForm` `validate` `valid` `true`
//
// `loading` `true` `$store.dispatch` `Login` Vuex `loading` `false`使 `setCookie` `Cookie` `15` `setCookie` `$router.push` `/``valid` `false` `false`
this.$refs.loginForm.validate(valid => {
if (valid) {
// let isSupport = getSupport();
// if(isSupport===undefined||isSupport==null){
// this.dialogVisible =true;
// return;
// }
this.loading = true;
this.$store.dispatch('Login', this.loginForm).then(() => {
this.loading = false;
setCookie("username",this.loginForm.username,15);
setCookie("password",this.loginForm.password,15);
this.$router.push({path: '/'})
}).catch(() => {
this.loading = false
})
} else {
console.log('参数验证不合法!');
return false
}
})
},
handleTry() {
// `dialogVisible` `true`
this.dialogVisible =true
},
dialogConfirm() {
// `dialogVisible` `false` `setSupport` `true` `setSupport`
this.dialogVisible =false;
setSupport(true);
},
dialogCancel() {
// `dialogVisible` `false` `setSupport` `false`
this.dialogVisible = false;
setSupport(false);
}
}
}
</script>
<style scoped>
// `login-form-layout` `absolute`使 `body` `0` `360px` `140px` `auto`

@ -0,0 +1,34 @@
<template> 
<brand-detail :is-edit='false'></brand-detail>
</template>
<script>
import BrandDetail from './components/BrandDetail'
export default {
name: 'addBrand',
components: { BrandDetail }
}
</script>
<style>
</style>
#abc
<template>
<!-- 使用BrandDetail组件并传递一个名为is-edit的属性值设置为false用于表示当前处于非编辑状态 -->
<brand-detail :is-edit='false'></brand-detail>
</template>
<script>
// BrandDetail./components/BrandDetailVue
import BrandDetail from './components/BrandDetail'
export default {
name: 'addBrand',
// BrandDetail使使
components: { BrandDetail }
}
</script>
<style>
/* 这里可以添加针对当前组件的样式规则,目前为空 */
</style>

@ -0,0 +1,145 @@
<template> 
<el-card class="form-container" shadow="never">
<el-form :model="brand" :rules="rules" ref="brandFrom" label-width="150px">
<el-form-item label="品牌名称:" prop="name">
<el-input v-model="brand.name"></el-input>
</el-form-item>
<el-form-item label="品牌首字母:">
<el-input v-model="brand.firstLetter"></el-input>
</el-form-item>
<el-form-item label="品牌LOGO" prop="logo">
<single-upload v-model="brand.logo"></single-upload>
</el-form-item>
<el-form-item label="品牌专区大图:">
<single-upload v-model="brand.bigPic"></single-upload>
</el-form-item>
<el-form-item label="品牌故事:">
<el-input
placeholder="请输入内容"
type="textarea"
v-model="brand.brandStory"
:autosize="true"></el-input>
</el-form-item>
<el-form-item label="排序:" prop="sort">
<el-input v-model.number="brand.sort"></el-input>
</el-form-item>
<el-form-item label="是否显示:">
<el-radio-group v-model="brand.showStatus">
<el-radio :label="1"></el-radio>
<el-radio :label="0"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="品牌制造商:">
<el-radio-group v-model="brand.factoryStatus">
<el-radio :label="1"></el-radio>
<el-radio :label="0"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit('brandFrom')"></el-button>
<el-button v-if="!isEdit" @click="resetForm('brandFrom')"></el-button>
</el-form-item>
</el-form>
</el-card>
</template>
<script>
import {createBrand, getBrand, updateBrand} from '@/api/brand'
import SingleUpload from '@/components/Upload/singleUpload'
const defaultBrand={
bigPic: '',
brandStory: '',
factoryStatus: 0,
firstLetter: '',
logo: '',
name: '',
showStatus: 0,
sort: 0
};
export default {
name: 'BrandDetail',
components:{SingleUpload},
props: {
isEdit: {
type: Boolean,
default: false
}
},
data() {
return {
brand:Object.assign({}, defaultBrand),
rules: {
name: [
{required: true, message: '请输入品牌名称', trigger: 'blur'},
{min: 2, max: 140, message: '长度在 2 到 140 个字符', trigger: 'blur'}
],
logo: [
{required: true, message: '请输入品牌logo', trigger: 'blur'}
],
sort: [
{type: 'number', message: '排序必须为数字'}
],
}
}
},
created() {
if (this.isEdit) {
getBrand(this.$route.query.id).then(response => {
this.brand = response.data;
});
}else{
this.brand = Object.assign({},defaultBrand);
}
},
methods: {
onSubmit(formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
this.$confirm('是否提交数据', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
if (this.isEdit) {
updateBrand(this.$route.query.id, this.brand).then(response => {
this.$refs[formName].resetFields();
this.$message({
message: '修改成功',
type: 'success',
duration:1000
});
this.$router.back();
});
} else {
createBrand(this.brand).then(response => {
this.$refs[formName].resetFields();
this.brand = Object.assign({},defaultBrand);
this.$message({
message: '提交成功',
type: 'success',
duration:1000
});
});
}
});
} else {
this.$message({
message: '验证失败',
type: 'error',
duration:1000
});
return false;
}
});
},
resetForm(formName) {
this.$refs[formName].resetFields();
this.brand = Object.assign({},defaultBrand);
}
}
}
</script>
<style>
</style>

@ -0,0 +1,674 @@
<template> 
<div class="app-container">
<el-card class="filter-container" shadow="never">
<div>
<i class="el-icon-search"></i>
<span>筛选搜索</span>
<el-button
style="float: right"
@click="searchBrandList()"
type="primary"
size="small">
查询结果
</el-button>
</div>
<div style="margin-top: 15px">
<el-form :inline="true" :model="listQuery" size="small" label-width="140px">
<el-form-item label="输入搜索:">
<el-input style="width: 203px" v-model="listQuery.keyword" placeholder="品牌名称/关键字"></el-input>
</el-form-item>
</el-form>
</div>
</el-card>
<el-card class="operate-container" shadow="never">
<i class="el-icon-tickets"></i>
<span>数据列表</span>
<el-button
class="btn-add"
@click="addBrand()"
size="mini">
添加
</el-button>
</el-card>
<div class="table-container">
<el-table ref="brandTable"
:data="list"
style="width: 100%"
@selection-change="handleSelectionChange"
v-loading="listLoading"
border>
<el-table-column type="selection" width="60" align="center"></el-table-column>
<el-table-column label="编号" width="100" align="center">
<template slot-scope="scope">{{scope.row.id}}</template>
</el-table-column>
<el-table-column label="品牌名称" align="center">
<template slot-scope="scope">{{scope.row.name}}</template>
</el-table-column>
<el-table-column label="品牌首字母" width="100" align="center">
<template slot-scope="scope">{{scope.row.firstLetter}}</template>
</el-table-column>
<el-table-column label="排序" width="100" align="center">
<template slot-scope="scope">{{scope.row.sort}}</template>
</el-table-column>
<el-table-column label="品牌制造商" width="100" align="center">
<template slot-scope="scope">
<el-switch
@change="handleFactoryStatusChange(scope.$index, scope.row)"
:active-value="1"
:inactive-value="0"
v-model="scope.row.factoryStatus">
</el-switch>
</template>
</el-table-column>
<el-table-column label="是否显示" width="100" align="center">
<template slot-scope="scope">
<el-switch
@change="handleShowStatusChange(scope.$index, scope.row)"
:active-value="1"
:inactive-value="0"
v-model="scope.row.showStatus">
</el-switch>
</template>
</el-table-column>
<el-table-column label="相关" width="220" align="center">
<template slot-scope="scope">
<span>商品</span>
<el-button
size="mini"
type="text"
@click="getProductList(scope.$index, scope.row)">100
</el-button>
<span>评价</span>
<el-button
size="mini"
type="text"
@click="getProductCommentList(scope.$index, scope.row)">1000
</el-button>
</template>
</el-table-column>
<el-table-column label="操作" width="200" align="center">
<template slot-scope="scope">
<el-button
size="mini"
@click="handleUpdate(scope.$index, scope.row)">编辑
</el-button>
<el-button
size="mini"
type="danger"
@click="handleDelete(scope.$index, scope.row)">删除
</el-button>
</template>
</el-table-column>
</el-table>
</div>
<div class="batch-operate-container">
<el-select
size="small"
v-model="operateType" placeholder="批量操作">
<el-option
v-for="item in operates"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
<el-button
style="margin-left: 20px"
class="search-button"
@click="handleBatchOperate()"
type="primary"
size="small">
确定
</el-button>
</div>
<div class="pagination-container">
<el-pagination
background
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
layout="total, sizes,prev, pager, next,jumper"
:page-size="listQuery.pageSize"
:page-sizes="[5,10,15]"
:current-page.sync="listQuery.pageNum"
:total="total">
</el-pagination>
</div>
</div>
</template>
<script>
import {fetchList, updateShowStatus, updateFactoryStatus, deleteBrand} from '@/api/brand'
export default {
name: 'brandList',
data() {
return {
operates: [
{
label: "显示品牌",
value: "showBrand"
},
{
label: "隐藏品牌",
value: "hideBrand"
}
],
operateType: null,
listQuery: {
keyword: null,
pageNum: 1,
pageSize: 10
},
list: null,
total: null,
listLoading: true,
multipleSelection: []
}
},
created() {
this.getList();
},
methods: {
getList() {
this.listLoading = true;
fetchList(this.listQuery).then(response => {
this.listLoading = false;
this.list = response.data.list;
this.total = response.data.total;
this.totalPage = response.data.totalPage;
this.pageSize = response.data.pageSize;
});
},
handleSelectionChange(val) {
this.multipleSelection = val;
},
handleUpdate(index, row) {
this.$router.push({path: '/pms/updateBrand', query: {id: row.id}})
},
handleDelete(index, row) {
this.$confirm('是否要删除该品牌', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
deleteBrand(row.id).then(response => {
this.$message({
message: '删除成功',
type: 'success',
duration: 1000
});
this.getList();
});
});
},
getProductList(index, row) {
console.log(index, row);
},
getProductCommentList(index, row) {
console.log(index, row);
},
handleFactoryStatusChange(index, row) {
var data = new URLSearchParams();
data.append("ids", row.id);
data.append("factoryStatus", row.factoryStatus);
updateFactoryStatus(data).then(response => {
this.$message({
message: '修改成功',
type: 'success',
duration: 1000
});
}).catch(error => {
if (row.factoryStatus === 0) {
row.factoryStatus = 1;
} else {
row.factoryStatus = 0;
}
});
},
handleShowStatusChange(index, row) {
let data = new URLSearchParams();
;
data.append("ids", row.id);
data.append("showStatus", row.showStatus);
updateShowStatus(data).then(response => {
this.$message({
message: '修改成功',
type: 'success',
duration: 1000
});
}).catch(error => {
if (row.showStatus === 0) {
row.showStatus = 1;
} else {
row.showStatus = 0;
}
});
},
handleSizeChange(val) {
this.listQuery.pageNum = 1;
this.listQuery.pageSize = val;
this.getList();
},
handleCurrentChange(val) {
this.listQuery.pageNum = val;
this.getList();
},
searchBrandList() {
this.listQuery.pageNum = 1;
this.getList();
},
handleBatchOperate() {
console.log(this.multipleSelection);
if (this.multipleSelection < 1) {
this.$message({
message: '请选择一条记录',
type: 'warning',
duration: 1000
});
return;
}
let showStatus = 0;
if (this.operateType === 'showBrand') {
showStatus = 1;
} else if (this.operateType === 'hideBrand') {
showStatus = 0;
} else {
this.$message({
message: '请选择批量操作类型',
type: 'warning',
duration: 1000
});
return;
}
let ids = [];
for (let i = 0; i < this.multipleSelection.length; i++) {
ids.push(this.multipleSelection[i].id);
}
let data = new URLSearchParams();
data.append("ids", ids);
data.append("showStatus", showStatus);
updateShowStatus(data).then(response => {
this.getList();
this.$message({
message: '修改成功',
type: 'success',
duration: 1000
});
});
},
addBrand() {
this.$router.push({path: '/pms/addBrand'})
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
</style>
#abc
<template>
<!-- 整个页面的最外层容器用于布局和包裹内部的各个组件与元素 -->
<div class="app-container">
<!-- 筛选搜索区域的卡片组件设置阴影效果为无 -->
<el-card class="filter-container" shadow="never">
<div>
<!-- 使用Element UI的图标组件展示搜索图标 -->
<i class="el-icon-search"></i>
<!-- 显示筛选搜索的文字说明 -->
<span>筛选搜索</span>
<!-- Element UI的按钮组件点击时触发searchBrandList方法样式设置为靠右类型为主要按钮尺寸为小 -->
<el-button
style="float: right"
@click="searchBrandList()"
type="primary"
size="small">
查询结果
</el-button>
</div>
<div style="margin-top: 15px">
<!-- 内联表单组件绑定数据模型为listQuery尺寸为小标签宽度为140px -->
<el-form :inline="true" :model="listQuery" size="small" label-width="140px">
<!-- 表单项标签显示为输入搜索 -->
<el-form-item label="输入搜索:">
<!-- 输入框组件绑定数据模型为listQuery的keyword属性宽度为203px有占位提示文字 -->
<el-input style="width: 203px" v-model="listQuery.keyword" placeholder="品牌名称/关键字"></el-input>
</el-form-item>
</el-form>
</div>
</el-card>
<!-- 操作区域的卡片组件设置阴影效果为无 -->
<el-card class="operate-container" shadow="never">
<!-- 使用Element UI的图标组件展示票务图标此处可能表意不太准确需根据实际情况确认合适图标含义 -->
<i class="el-icon-tickets"></i>
<!-- 显示数据列表的文字说明 -->
<span>数据列表</span>
<!-- Element UI的按钮组件点击时触发addBrand方法尺寸为迷你 -->
<el-button
class="btn-add"
@click="addBrand()"
size="mini">
添加
</el-button>
</el-card>
<!-- 表格容器用于放置展示数据的表格 -->
<div class="table-container">
<!-- Element UI的表格组件绑定数据为list宽度为100%监听选择行变化事件加载状态变化设置边框 -->
<el-table ref="brandTable"
:data="list"
style="width: 100%"
@selection-change="handleSelectionChange"
v-loading="listLoading"
border>
<!-- 表格列类型为选择框宽度为60px内容居中对齐 -->
<el-table-column type="selection" width="60" align="center"></el-table-column>
<!-- 表格列标签显示为编号宽度为100px内容居中对齐通过插槽作用域展示对应行的数据中的id属性 -->
<el-table-column label="编号" width="100" align="center">
<template slot-scope="scope">{{scope.row.id}}</template>
</el-table-column>
<!-- 表格列标签显示为品牌名称内容居中对齐通过插槽作用域展示对应行的数据中的name属性 -->
<el-table-column label="品牌名称" align="center">
<template slot-scope="scope">{{scope.row.name}}</template>
</el-table-column>
<!-- 表格列标签显示为品牌首字母宽度为100px内容居中对齐通过插槽作用域展示对应行的数据中的firstLetter属性 -->
<el-table-column label="品牌首字母" width="100" align="center">
<template slot-scope="scope">{{scope.row.firstLetter}}</template>
</el-table-column>
<!-- 表格列标签显示为排序宽度为100px内容居中对齐通过插槽作用域展示对应行的数据中的sort属性 -->
<el-table-column label="排序" width="100" align="center">
<template slot-scope="scope">{{scope.row.sort}}</template>
</el-table-column>
<!-- 表格列标签显示为品牌制造商宽度为100px内容居中对齐内部包含一个开关组件用于切换工厂状态绑定对应行的数据中的factoryStatus属性并监听状态改变事件 -->
<el-table-column label="品牌制造商" width="100" align="center">
<template slot-scope="scope">
<el-switch
@change="handleFactoryStatusChange(scope.$index, scope.row)"
:active-value="1"
:inactive-value="0"
v-model="scope.row.factoryStatus">
</el-switch>
</template>
</el-table-column>
<!-- 表格列标签显示为是否显示宽度为100px内容居中对齐内部包含一个开关组件用于切换显示状态绑定对应行的数据中的showStatus属性并监听状态改变事件 -->
<el-table-column label="是否显示" width="100" align="center">
<template slot-scope="scope">
<el-switch
@change="handleShowStatusChange(scope.$index, scope.row)"
:active-value="1"
:inactive-value="0"
v-model="scope.row.showStatus">
</el-switch>
</template>
</el-table-column>
<!-- 表格列标签显示为相关宽度为220px内容居中对齐内部包含查看商品评价相关的按钮点击可触发对应的方法并传递对应行数据 -->
<el-table-column label="相关" width="220" align="center">
<template slot-scope="scope">
<span>商品</span>
<el-button
size="mini"
type="text"
@click="getProductList(scope.$index, scope.row)">100
</el-button>
<span>评价</span>
<el-button
size="mini"
type="text"
@click="getProductCommentList(scope.$index, scope.row)">1000
</el-button>
</template>
</el-table-column>
<!-- 表格列标签显示为操作宽度为200px内容居中对齐内部包含编辑删除按钮点击可触发对应的操作方法并传递对应行数据 -->
<el-table-column label="操作" width="200" align="center">
<template slot-scope="scope">
<el-button
size="mini"
@click="handleUpdate(scope.$index, scope.row)">编辑
</el-button>
<el-button
size="mini"
type="danger"
@click="handleDelete(scope.$index, scope.row)">删除
</el-button>
</template>
</el-table-column>
</el-table>
</div>
<!-- 批量操作区域的容器 -->
<div class="batch-operate-container">
<!-- 下拉选择框组件绑定数据模型为operateType有占位提示文字 -->
<el-select
size="small"
v-model="operateType" placeholder="批量操作">
<!-- 通过循环生成下拉选项每个选项的标签和值根据operates数组中的元素来确定 -->
<el-option
v-for="item in operates"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
<!-- Element UI的按钮组件点击时触发handleBatchOperate方法样式设置有一定间隔类型为主要按钮尺寸为小 -->
<el-button
style="margin-left: 20px"
class="search-button"
@click="handleBatchOperate()"
type="primary"
size="small">
确定
</el-button>
</div>
<!-- 分页容器用于放置分页组件 -->
<div class="pagination-container">
<!-- Element UI的分页组件设置背景色监听页面尺寸和当前页变化事件布局包含多种分页相关元素绑定相关数据属性 -->
<el-pagination
background
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
layout="total, sizes,prev, pager, next,jumper"
:page-size="listQuery.pageSize"
:page-sizes="[5,10,15]"
:current-page.sync="listQuery.pageNum"
:total="total">
</el-pagination>
</div>
</div>
</template>
<script>
// @/api/brand
import {fetchList, updateShowStatus, updateFactoryStatus, deleteBrand} from '@/api/brand'
export default {
name: 'brandList',
data() {
return {
//
operates: [
{
label: "显示品牌",
value: "showBrand"
},
{
label: "隐藏品牌",
value: "hideBrand"
}
],
// null
operateType: null,
//
listQuery: {
keyword: null,
pageNum: 1,
pageSize: 10
},
// null
list: null,
// null
total: null,
// true
listLoading: true,
//
multipleSelection: []
}
},
created() {
// getList
this.getList();
},
methods: {
getList() {
// true
this.listLoading = true;
// fetchList
fetchList(this.listQuery).then(response => {
this.listLoading = false;
this.list = response.data.list;
this.total = response.data.total;
this.totalPage = response.data.totalPage;
this.pageSize = response.data.pageSize;
});
},
handleSelectionChange(val) {
// multipleSelection
this.multipleSelection = val;
},
handleUpdate(index, row) {
// id
this.$router.push({path: '/pms/updateBrand', query: {id: row.id}})
},
handleDelete(index, row) {
// deleteBrand
this.$confirm('是否要删除该品牌', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
deleteBrand(row.id).then(response => {
this.$message({
message: '删除成功',
type: 'success',
duration: 1000
});
this.getList();
});
});
},
getProductList(index, row) {
//
console.log(index, row);
},
getProductCommentList(index, row) {
//
console.log(index, row);
},
handleFactoryStatusChange(index, row) {
// URLSearchParamsidupdateFactoryStatus
var data = new URLSearchParams();
data.append("ids", row.id);
data.append("factoryStatus", row.factoryStatus);
updateFactoryStatus(data).then(response => {
this.$message({
message: '修改成功',
type: 'success',
duration: 1000
});
}).catch(error => {
if (row.factoryStatus === 0) {
row.factoryStatus = 1;
} else {
row.factoryStatus = 0;
}
});
},
handleShowStatusChange(index, row) {
// URLSearchParamsidupdateShowStatus
let data = new URLSearchParams();
data.append("ids", row.id);
data.append("showStatus", row.showStatus);
updateShowStatus(data).then(response => {
this.$message({
message: '修改成功',
type: 'success',
duration: 1000
});
}).catch(error => {
if (row.showStatus === 0) {
row.showStatus = 1;
} else {
row.showStatus = 0;
}
});
},
handleSizeChange(val) {
// 1
this.listQuery.pageNum = 1;
this.listQuery.pageSize = val;
this.getList();
},
handleCurrentChange(val) {
//
this.listQuery.pageNum = val;
this.getList();
},
searchBrandList() {
// 1
this.listQuery.pageNum = 1;
this.getList();
},
handleBatchOperate() {
console.log(this.multipleSelection);
//
if (this.multipleSelection.length < 1) {
this.$message({
message: '请选择一条记录',
type: 'warning',
duration: 1000
});
return;
}
let showStatus = 0;
//
if (this.operateType === 'showBrand') {
showStatus = 1;
} else if (this.operateType === 'hideBrand') {
showStatus = 0;
} else {
//
this.$message({
message: '请选择批量操作类型',
type: 'warning',
duration: 1000
});
return;
}
let ids = [];
// idids
for (let i = 0; i < this.multipleSelection.length; i++) {
ids.push(this.multipleSelection[i].id);
}
let data = new URLSearchParams();
data.append("ids", ids);
data.append("showStatus", showStatus);
// updateShowStatus
updateShowStatus(data).then(response => {
this.getList();
this.$message({
message: '修改成功',
type: 'success',
duration: 1000
});
});
},
addBrand() {
//
this.$router.push({path: '/pms/addBrand'})
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
/*
scoped属性表示样式只作用于当前组件内部的元素避免样式污染其他组件 */
</

@ -0,0 +1,37 @@
<template> 
<brand-detail :is-edit='true'></brand-detail>
</template>
<script>
import BrandDetail from './components/BrandDetail'
export default {
name: 'updateBrand',
components: { BrandDetail }
}
</script>
<style>
</style>
#abc
<template>
<!-- 使用BrandDetail组件并传递一个名为is-edit的属性值设置为true意味着当前处于编辑状态
可能用于控制BrandDetail组件内部呈现出可编辑的界面等相关功能 -->
<brand-detail :is-edit='true'></brand-detail>
</template>
<script>
// './components/BrandDetail'BrandDetailVue
// 使
import BrandDetail from './components/BrandDetail'
export default {
name: 'updateBrand',
// BrandDetail使template使
//
components: { BrandDetail }
}
</script>
<style>
/* CSS
比如对组件内元素的布局颜色字体等样式相关的设置 */
</style>

@ -0,0 +1,30 @@
<template> 
<product-detail :is-edit='false'></product-detail>
</template>
<script>
import ProductDetail from './components/ProductDetail'
export default {
name: 'addProduct',
components: { ProductDetail }
}
</script>
<style>
</style>
#abc
<template>
<!-- 这里使用了名为 `product-detail` 的自定义组件通过 `:is-edit='false'` 属性绑定 `product-detail` 组件传递了一个名为 `is-edit` 的属性值为 `false`意味着当前是新增商品的操作模式而非编辑商品模式`product-detail` 组件应该会根据这个属性来展示相应的界面和执行对应的逻辑 -->
<product-detail :is-edit='false'></product-detail>
</template>
<script>
// `./components/` `ProductDetail`
import ProductDetail from './components/ProductDetail';
export default {
name: 'addProduct',
// `components` `ProductDetail` 使`addProduct`使 `<product-detail>` Vue.js
components: { ProductDetail }
}
</script>
<style>
/* 这里的样式部分目前为空,可根据 `addProduct` 组件整体的视觉设计需求,添加相应的 CSS 样式规则,比如设置组件的布局、大小、颜色等样式属性,使其在页面中呈现出符合预期的外观效果 */
</style>

@ -0,0 +1,952 @@
<template>
<div style="margin-top: 50px">
<el-form :model="value" ref="productAttrForm" label-width="120px" class="form-inner-container" size="small">
<el-form-item label="属性类型:">
<el-select v-model="value.productAttributeCategoryId"
placeholder="请选择属性类型"
@change="handleProductAttrChange">
<el-option
v-for="item in productAttributeCategoryOptions"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="商品规格:">
<el-card shadow="never" class="cardBg">
<div v-for="(productAttr,idx) in selectProductAttr">
{{productAttr.name}}
<el-checkbox-group v-if="productAttr.handAddStatus===0" v-model="selectProductAttr[idx].values">
<el-checkbox v-for="item in getInputListArr(productAttr.inputList)" :label="item" :key="item"
class="littleMarginLeft"></el-checkbox>
</el-checkbox-group>
<div v-else>
<el-checkbox-group v-model="selectProductAttr[idx].values">
<div v-for="(item,index) in selectProductAttr[idx].options" style="display: inline-block"
class="littleMarginLeft">
<el-checkbox :label="item" :key="item"></el-checkbox>
<el-button type="text" class="littleMarginLeft" @click="handleRemoveProductAttrValue(idx,index)">
</el-button>
</div>
</el-checkbox-group>
<el-input v-model="addProductAttrValue" style="width: 160px;margin-left: 10px" clearable></el-input>
<el-button class="littleMarginLeft" @click="handleAddProductAttrValue(idx)"></el-button>
</div>
</div>
</el-card>
<el-table style="width: 100%;margin-top: 20px"
:data="value.skuStockList"
border>
<el-table-column
v-for="(item,index) in selectProductAttr"
:label="item.name"
:key="item.id"
align="center">
<template slot-scope="scope">
{{getProductSkuSp(scope.row,index)}}
</template>
</el-table-column>
<el-table-column
label="销售价格"
width="100"
align="center">
<template slot-scope="scope">
<el-input v-model="scope.row.price"></el-input>
</template>
</el-table-column>
<el-table-column
label="促销价格"
width="100"
align="center">
<template slot-scope="scope">
<el-input v-model="scope.row.promotionPrice"></el-input>
</template>
</el-table-column>
<el-table-column
label="商品库存"
width="80"
align="center">
<template slot-scope="scope">
<el-input v-model="scope.row.stock"></el-input>
</template>
</el-table-column>
<el-table-column
label="库存预警值"
width="80"
align="center">
<template slot-scope="scope">
<el-input v-model="scope.row.lowStock"></el-input>
</template>
</el-table-column>
<el-table-column
label="SKU编号"
width="160"
align="center">
<template slot-scope="scope">
<el-input v-model="scope.row.skuCode"></el-input>
</template>
</el-table-column>
<el-table-column
label="操作"
width="80"
align="center">
<template slot-scope="scope">
<el-button
type="text"
@click="handleRemoveProductSku(scope.$index, scope.row)">删除
</el-button>
</template>
</el-table-column>
</el-table>
<el-button
type="primary"
style="margin-top: 20px"
@click="handleRefreshProductSkuList">刷新列表
</el-button>
<el-button
type="primary"
style="margin-top: 20px"
@click="handleSyncProductSkuPrice">同步价格
</el-button>
<el-button
type="primary"
style="margin-top: 20px"
@click="handleSyncProductSkuStock">同步库存
</el-button>
</el-form-item>
<el-form-item label="属性图片:" v-if="hasAttrPic">
<el-card shadow="never" class="cardBg">
<div v-for="(item,index) in selectProductAttrPics">
<span>{{item.name}}:</span>
<single-upload v-model="item.pic"
style="width: 300px;display: inline-block;margin-left: 10px"></single-upload>
</div>
</el-card>
</el-form-item>
<el-form-item label="商品参数:">
<el-card shadow="never" class="cardBg">
<div v-for="(item,index) in selectProductParam" :class="{littleMarginTop:index!==0}">
<div class="paramInputLabel">{{item.name}}:</div>
<el-select v-if="item.inputType===1" class="paramInput" v-model="selectProductParam[index].value">
<el-option
v-for="item in getParamInputList(item.inputList)"
:key="item"
:label="item"
:value="item">
</el-option>
</el-select>
<el-input v-else class="paramInput" v-model="selectProductParam[index].value"></el-input>
</div>
</el-card>
</el-form-item>
<el-form-item label="商品相册:">
<multi-upload v-model="selectProductPics"></multi-upload>
</el-form-item>
<el-form-item label="商品详情:">
<el-tabs v-model="activeHtmlName" type="card">
<el-tab-pane label="电脑端详情" name="pc">
<tinymce :width="595" :height="300" v-model="value.detailHtml"></tinymce>
</el-tab-pane>
<el-tab-pane label="移动端详情" name="mobile">
<tinymce :width="595" :height="300" v-model="value.detailMobileHtml"></tinymce>
</el-tab-pane>
</el-tabs>
</el-form-item>
<el-form-item style="text-align: center">
<el-button size="medium" @click="handlePrev"></el-button>
<el-button type="primary" size="medium" @click="handleNext"></el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
import {fetchList as fetchProductAttrCateList} from '@/api/productAttrCate'
import {fetchList as fetchProductAttrList} from '@/api/productAttr'
import SingleUpload from '@/components/Upload/singleUpload'
import MultiUpload from '@/components/Upload/multiUpload'
import Tinymce from '@/components/Tinymce'
export default {
name: "ProductAttrDetail",
components: {SingleUpload, MultiUpload, Tinymce},
props: {
value: Object,
isEdit: {
type: Boolean,
default: false
}
},
data() {
return {
//
hasEditCreated:false,
//
productAttributeCategoryOptions: [],
//
selectProductAttr: [],
//
selectProductParam: [],
//
selectProductAttrPics: [],
//
addProductAttrValue: '',
//
activeHtmlName: 'pc'
}
},
computed: {
//
hasAttrPic() {
if (this.selectProductAttrPics.length < 1) {
return false;
}
return true;
},
//
productId(){
return this.value.id;
},
//
selectProductPics:{
get:function () {
let pics=[];
if(this.value.pic===undefined||this.value.pic==null||this.value.pic===''){
return pics;
}
pics.push(this.value.pic);
if(this.value.albumPics===undefined||this.value.albumPics==null||this.value.albumPics===''){
return pics;
}
let albumPics = this.value.albumPics.split(',');
for(let i=0;i<albumPics.length;i++){
pics.push(albumPics[i]);
}
return pics;
},
set:function (newValue) {
if (newValue == null || newValue.length === 0) {
this.value.pic = null;
this.value.albumPics = null;
} else {
this.value.pic = newValue[0];
this.value.albumPics = '';
if (newValue.length > 1) {
for (let i = 1; i < newValue.length; i++) {
this.value.albumPics += newValue[i];
if (i !== newValue.length - 1) {
this.value.albumPics += ',';
}
}
}
}
}
}
},
created() {
this.getProductAttrCateList();
},
watch: {
productId:function (newValue) {
if(!this.isEdit)return;
if(this.hasEditCreated)return;
if(newValue===undefined||newValue==null||newValue===0)return;
this.handleEditCreated();
}
},
methods: {
handleEditCreated() {
//id
if(this.value.productAttributeCategoryId!=null){
this.handleProductAttrChange(this.value.productAttributeCategoryId);
}
this.hasEditCreated=true;
},
getProductAttrCateList() {
let param = {pageNum: 1, pageSize: 100};
fetchProductAttrCateList(param).then(response => {
this.productAttributeCategoryOptions = [];
let list = response.data.list;
for (let i = 0; i < list.length; i++) {
this.productAttributeCategoryOptions.push({label: list[i].name, value: list[i].id});
}
});
},
getProductAttrList(type, cid) {
let param = {pageNum: 1, pageSize: 100, type: type};
fetchProductAttrList(cid, param).then(response => {
let list = response.data.list;
if (type === 0) {
this.selectProductAttr = [];
for (let i = 0; i < list.length; i++) {
let options = [];
let values = [];
if (this.isEdit) {
if (list[i].handAddStatus === 1) {
//
options = this.getEditAttrOptions(list[i].id);
}
//
values = this.getEditAttrValues(i);
}
this.selectProductAttr.push({
id: list[i].id,
name: list[i].name,
handAddStatus: list[i].handAddStatus,
inputList: list[i].inputList,
values: values,
options: options
});
}
if(this.isEdit){
//
this.refreshProductAttrPics();
}
} else {
this.selectProductParam = [];
for (let i = 0; i < list.length; i++) {
let value=null;
if(this.isEdit){
//
value= this.getEditParamValue(list[i].id);
}
this.selectProductParam.push({
id: list[i].id,
name: list[i].name,
value: value,
inputType: list[i].inputType,
inputList: list[i].inputList
});
}
}
});
},
//
getEditAttrOptions(id) {
let options = [];
for (let i = 0; i < this.value.productAttributeValueList.length; i++) {
let attrValue = this.value.productAttributeValueList[i];
if (attrValue.productAttributeId === id) {
let strArr = attrValue.value.split(',');
for (let j = 0; j < strArr.length; j++) {
options.push(strArr[j]);
}
break;
}
}
return options;
},
//
getEditAttrValues(index) {
let values = new Set();
if (index === 0) {
for (let i = 0; i < this.value.skuStockList.length; i++) {
let sku = this.value.skuStockList[i];
let spData = JSON.parse(sku.spData);
if (spData!= null && spData.length>=1) {
values.add(spData[0].value);
}
}
} else if (index === 1) {
for (let i = 0; i < this.value.skuStockList.length; i++) {
let sku = this.value.skuStockList[i];
let spData = JSON.parse(sku.spData);
if (spData!= null && spData.length>=2) {
values.add(spData[1].value);
}
}
} else {
for (let i = 0; i < this.value.skuStockList.length; i++) {
let sku = this.value.skuStockList[i];
let spData = JSON.parse(sku.spData);
if (spData!= null && spData.length>=3) {
values.add(spData[2].value);
}
}
}
return Array.from(values);
},
//
getEditParamValue(id){
for(let i=0;i<this.value.productAttributeValueList.length;i++){
if(id===this.value.productAttributeValueList[i].productAttributeId){
return this.value.productAttributeValueList[i].value;
}
}
},
handleProductAttrChange(value) {
this.getProductAttrList(0, value);
this.getProductAttrList(1, value);
},
getInputListArr(inputList) {
return inputList.split(',');
},
handleAddProductAttrValue(idx) {
let options = this.selectProductAttr[idx].options;
if (this.addProductAttrValue == null || this.addProductAttrValue == '') {
this.$message({
message: '属性值不能为空',
type: 'warning',
duration: 1000
});
return
}
if (options.indexOf(this.addProductAttrValue) !== -1) {
this.$message({
message: '属性值不能重复',
type: 'warning',
duration: 1000
});
return;
}
this.selectProductAttr[idx].options.push(this.addProductAttrValue);
this.addProductAttrValue = null;
},
handleRemoveProductAttrValue(idx, index) {
this.selectProductAttr[idx].options.splice(index, 1);
},
getProductSkuSp(row, index) {
let spData = JSON.parse(row.spData);
if(spData!=null&&index<spData.length){
return spData[index].value;
}else{
return null;
}
},
handleRefreshProductSkuList() {
this.$confirm('刷新列表将导致sku信息重新生成是否要刷新', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.refreshProductAttrPics();
this.refreshProductSkuList();
});
},
handleSyncProductSkuPrice(){
this.$confirm('将同步第一个sku的价格到所有sku,是否继续', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
if(this.value.skuStockList!==null&&this.value.skuStockList.length>0){
let tempSkuList = [];
tempSkuList = tempSkuList.concat(tempSkuList,this.value.skuStockList);
let price=this.value.skuStockList[0].price;
for(let i=0;i<tempSkuList.length;i++){
tempSkuList[i].price=price;
}
this.value.skuStockList=[];
this.value.skuStockList=this.value.skuStockList.concat(this.value.skuStockList,tempSkuList);
}
});
},
handleSyncProductSkuStock(){
this.$confirm('将同步第一个sku的库存到所有sku,是否继续', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
if(this.value.skuStockList!==null&&this.value.skuStockList.length>0){
let tempSkuList = [];
tempSkuList = tempSkuList.concat(tempSkuList,this.value.skuStockList);
let stock=this.value.skuStockList[0].stock;
let lowStock=this.value.skuStockList[0].lowStock;
for(let i=0;i<tempSkuList.length;i++){
tempSkuList[i].stock=stock;
tempSkuList[i].lowStock=lowStock;
}
this.value.skuStockList=[];
this.value.skuStockList=this.value.skuStockList.concat(this.value.skuStockList,tempSkuList);
}
});
},
refreshProductSkuList() {
this.value.skuStockList = [];
let skuList = this.value.skuStockList;
//
if (this.selectProductAttr.length === 1) {
let attr = this.selectProductAttr[0];
for (let i = 0; i < attr.values.length; i++) {
skuList.push({
spData: JSON.stringify([{key:attr.name,value:attr.values[i]}])
});
}
} else if (this.selectProductAttr.length === 2) {
let attr0 = this.selectProductAttr[0];
let attr1 = this.selectProductAttr[1];
for (let i = 0; i < attr0.values.length; i++) {
if (attr1.values.length === 0) {
skuList.push({
spData: JSON.stringify([{key:attr0.name,value:attr0.values[i]}])
});
continue;
}
for (let j = 0; j < attr1.values.length; j++) {
let spData = [];
spData.push({key:attr0.name,value:attr0.values[i]});
spData.push({key:attr1.name,value:attr1.values[j]});
skuList.push({
spData: JSON.stringify(spData)
});
}
}
} else {
let attr0 = this.selectProductAttr[0];
let attr1 = this.selectProductAttr[1];
let attr2 = this.selectProductAttr[2];
for (let i = 0; i < attr0.values.length; i++) {
if (attr1.values.length === 0) {
skuList.push({
spData: JSON.stringify([{key:attr0.name,value:attr0.values[i]}])
});
continue;
}
for (let j = 0; j < attr1.values.length; j++) {
if (attr2.values.length === 0) {
let spData = [];
spData.push({key:attr0.name,value:attr0.values[i]});
spData.push({key:attr1.name,value:attr1.values[j]});
skuList.push({
spData: JSON.stringify(spData)
});
continue;
}
for (let k = 0; k < attr2.values.length; k++) {
let spData = [];
spData.push({key:attr0.name,value:attr0.values[i]});
spData.push({key:attr1.name,value:attr1.values[j]});
spData.push({key:attr2.name,value:attr2.values[k]});
skuList.push({
spData: JSON.stringify(spData)
});
}
}
}
}
},
refreshProductAttrPics() {
this.selectProductAttrPics = [];
if (this.selectProductAttr.length >= 1) {
let values = this.selectProductAttr[0].values;
for (let i = 0; i < values.length; i++) {
let pic=null;
if(this.isEdit){
//
pic=this.getProductSkuPic(values[i]);
}
this.selectProductAttrPics.push({name: values[i], pic: pic})
}
}
},
//
getProductSkuPic(name){
for(let i=0;i<this.value.skuStockList.length;i++){
let spData = JSON.parse(this.value.skuStockList[i].spData);
if(name===spData[0].value){
return this.value.skuStockList[i].pic;
}
}
return null;
},
//
mergeProductAttrValue() {
this.value.productAttributeValueList = [];
for (let i = 0; i < this.selectProductAttr.length; i++) {
let attr = this.selectProductAttr[i];
if (attr.handAddStatus === 1 && attr.options != null && attr.options.length > 0) {
this.value.productAttributeValueList.push({
productAttributeId: attr.id,
value: this.getOptionStr(attr.options)
});
}
}
for (let i = 0; i < this.selectProductParam.length; i++) {
let param = this.selectProductParam[i];
this.value.productAttributeValueList.push({
productAttributeId: param.id,
value: param.value
});
}
},
//
mergeProductAttrPics() {
for (let i = 0; i < this.selectProductAttrPics.length; i++) {
for (let j = 0; j < this.value.skuStockList.length; j++) {
let spData = JSON.parse(this.value.skuStockList[j].spData);
if (spData[0].value === this.selectProductAttrPics[i].name) {
this.value.skuStockList[j].pic = this.selectProductAttrPics[i].pic;
}
}
}
},
getOptionStr(arr) {
let str = '';
for (let i = 0; i < arr.length; i++) {
str += arr[i];
if (i != arr.length - 1) {
str += ',';
}
}
return str;
},
handleRemoveProductSku(index, row) {
let list = this.value.skuStockList;
if (list.length === 1) {
list.pop();
} else {
list.splice(index, 1);
}
},
getParamInputList(inputList) {
return inputList.split(',');
},
handlePrev() {
this.$emit('prevStep')
},
handleNext() {
this.mergeProductAttrValue();
this.mergeProductAttrPics();
this.$emit('nextStep')
}
}
}
</script>
<style scoped>
.littleMarginLeft {
margin-left: 10px;
}
.littleMarginTop {
margin-top: 10px;
}
.paramInput {
width: 250px;
}
.paramInputLabel {
display: inline-block;
width: 100px;
text-align: right;
padding-right: 10px
}
.cardBg {
background: #F8F9FC;
}
</style>
#abc
<template>
<!-- 整体的外层 div设置了距离顶部 50px margin用于页面布局上的间隔 -->
<div style="margin-top: 50px">
<!-- el-form 表单组件绑定数据模型为 value设置了表单引用名标签宽度类名以及尺寸为小用于收集和展示商品相关信息 -->
<el-form :model="value" ref="productAttrForm" label-width="120px" class="form-inner-container" size="small">
<!-- 表单项标签显示为属性类型 -->
<el-form-item label="属性类型:">
<!-- el-select 下拉选择框组件绑定 v-model value.productAttributeCategoryId有占位提示文字并且监听 change 事件 -->
<el-select v-model="value.productAttributeCategoryId"
placeholder="请选择属性类型"
@change="handleProductAttrChange">
<!-- 通过循环生成下拉选项每个选项的标签和值根据 productAttributeCategoryOptions 数组中的元素来确定 -->
<el-option
v-for="item in productAttributeCategoryOptions"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<!-- 表单项标签显示为商品规格用于展示和操作商品规格相关内容 -->
<el-form-item label="商品规格:">
<!-- el-card 卡片组件设置阴影效果为无添加了自定义类名 cardBg用于对商品规格部分进行布局划分 -->
<el-card shadow="never" class="cardBg">
<!-- 循环遍历 selectProductAttr 数组展示每个商品属性相关内容 -->
<div v-for="(productAttr,idx) in selectProductAttr">
<!-- 显示商品属性的名称 -->
{{productAttr.name}}
<!-- 根据商品属性的 handAddStatus 判断使用不同的复选框组展示方式当为 0 时使用固定的输入列表生成复选框 -->
<el-checkbox-group v-if="productAttr.handAddStatus===0" v-model="selectProductAttr[idx].values">
<el-checkbox v-for="item in getInputListArr(productAttr.inputList)" :label="item" :key="item"
class="littleMarginLeft"></el-checkbox>
</el-checkbox-group>
<!-- handAddStatus 不为 0 展示可动态操作的复选框组包含添加删除选项等功能 -->
<div v-else>
<el-checkbox-group v-model="selectProductAttr[idx].values">
<div v-for="(item,index) in selectProductAttr[idx].options" style="display: inline-block"
class="littleMarginLeft">
<el-checkbox :label="item" :key="item"></el-checkbox>
<el-button type="text" class="littleMarginLeft" @click="handleRemoveProductAttrValue(idx,index)">
</el-button>
</div>
</el-checkbox-group>
<el-input v-model="addProductAttrValue" style="width: 160px;margin-left: 10px" clearable></el-input>
<el-button class="littleMarginLeft" @click="handleAddProductAttrValue(idx)"></el-button>
</div>
</div>
</el-card>
<!-- el-table 表格组件设置宽度为 100%绑定数据为 value.skuStockList设置边框用于展示商品 SKU 相关信息 -->
<el-table style="width: 100%;margin-top: 20px"
:data="value.skuStockList"
border>
<!-- 循环生成表格列根据 selectProductAttr 数组中的每个商品属性生成对应列用于展示每个 SKU 对应的属性值 -->
<el-table-column
v-for="(item,index) in selectProductAttr"
:label="item.name"
:key="item.id"
align="center">
<template slot-scope="scope">
{{getProductSkuSp(scope.row,index)}}
</template>
</el-table-column>
<!-- 表格列标签显示为销售价格宽度为 100px内容居中对齐内部使用输入框展示和编辑价格 -->
<el-table-column
label="销售价格"
width="100"
align="center">
<template slot-scope="scope">
<el-input v-model="scope.row.price"></el-input>
</template>
</el-table-column>
<!-- 表格列标签显示为促销价格宽度为 100px内容居中对齐内部使用输入框展示和编辑促销价格 -->
<el-table-column
label="促销价格"
width="100"
align="center">
<template slot-scope="scope">
<el-input v-model="scope.row.promotionPrice"></el-input>
</template>
</el-table-column>
<!-- 表格列标签显示为商品库存宽度为 80px内容居中对齐内部使用输入框展示和编辑库存数量 -->
<el-table-column
label="商品库存"
width="80"
align="center">
<template slot-scope="scope">
<el-input v-model="scope.row.stock"></el-input>
</template>
</el-table-column>
<!-- 表格列标签显示为库存预警值宽度为 80px内容居中对齐内部使用输入框展示和编辑库存预警值 -->
<el-table-column
label="库存预警值"
width="80"
align="center">
<template slot-scope="scope">
<el-input v-model="scope.row.lowStock"></el-input>
</template>
</el-table-column>
<!-- 表格列标签显示为SKU 编号宽度为 160px内容居中对齐内部使用输入框展示和编辑 SKU 编号 -->
<el-table-column
label="SKU 编号"
width="160"
align="center">
<template slot-scope="scope">
<el-input v-model="scope.row.skuCode"></el-input>
</template>
</el-table-column>
<!-- 表格列标签显示为操作宽度为 80px内容居中对齐内部包含删除按钮点击可删除对应行的 SKU 数据 -->
<el-table-column
label="操作"
width="80"
align="center">
<template slot-scope="scope">
<el-button
type="text"
@click="handleRemoveProductSku(scope.$index, scope.row)">删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- el-button 按钮组件类型为主要按钮点击时触发 handleRefreshProductSkuList 方法用于刷新商品 SKU 列表 -->
<el-button
type="primary"
style="margin-top: 20px"
@click="handleRefreshProductSkuList">刷新列表
</el-button>
<!-- el-button 按钮组件类型为主要按钮点击时触发 handleSyncProductSkuPrice 方法用于同步商品 SKU 的价格 -->
<el-button
type="primary"
style="margin-top: 20px"
@click="handleSyncProductSkuPrice">同步价格
</el-button>
<!-- el-button 按钮组件类型为主要按钮点击时触发 handleSyncProductSkuStock 方法用于同步商品 SKU 的库存 -->
<el-button
type="primary"
style="margin-top: 20px"
@click="handleSyncProductSkuStock">同步库存
</el-button>
</el-form-item>
<!-- 根据 hasAttrPic 计算属性判断是否显示商品属性图片相关内容只有当有属性图片时才展示 -->
<el-form-item label="属性图片:" v-if="hasAttrPic">
<!-- el-card 卡片组件设置阴影效果为无添加了自定义类名 cardBg用于对商品属性图片部分进行布局划分 -->
<el-card shadow="never" class="cardBg">
<!-- 循环遍历 selectProductAttrPics 数组展示每个商品属性图片相关内容使用 single-upload 组件上传图片 -->
<div v-for="(item,index) in selectProductAttrPics">
<span>{{item.name}}:</span>
<single-upload v-model="item.pic"
style="width: 300px;display: inline-block;margin-left: 10px"></single-upload>
</div>
</el-card>
</el-form-item>
<!-- 表单项标签显示为商品参数用于展示和编辑商品参数相关内容 -->
<el-form-item label="商品参数:">
<!-- el-card 卡片组件设置阴影效果为无添加了自定义类名 cardBg用于对商品参数部分进行布局划分 -->
<el-card shadow="never" class="cardBg">
<!-- 循环遍历 selectProductParam 数组展示每个商品参数相关内容根据输入类型选择不同的输入组件展示和编辑参数值 -->
<div v-for="(item,index) in selectProductParam" :class="{littleMarginTop:index!==0}">
<div class="paramInputLabel">{{item.name}}:</div>
<el-select v-if="item.inputType===1" class="paramInput" v-model="selectProductParam[index].value">
<el-option
v-for="item in getParamInputList(item.inputList)"
:key="item"
:label="item"
:value="item">
</el-option>
</el-select>
<el-input v-else class="paramInput" v-model="selectProductParam[index].value"></el-input>
</div>
</el-card>
</el-form-item>
<!-- 表单项标签显示为商品相册使用 multi-upload 组件用于上传商品的相册图片 -->
<el-form-item label="商品相册:">
<multi-upload v-model="selectProductPics"></multi-upload>
</el-form-item>
<!-- 表单项标签显示为商品详情使用 el-tabs 选项卡组件展示电脑端和移动端的商品详情分别使用 tinymce 富文本编辑器进行编辑 -->
<el-form-item label="商品详情:">
<el-tabs v-model="activeHtmlName" type="card">
<el-tab-pane label="电脑端详情" name="pc">
<tinymce :width="595" :height="300" v-model="value.detailHtml"></tinymce>
</el-tab-pane>
<el-tab-pane label="移动端详情" name="mobile">
<tinymce :width="595" :height="300" v-model="value.detailMobileHtml"></tinymce>
</el-tab-pane>
</el-tabs>
</el-form-item>
<!-- 表单项用于放置操作按钮设置文本居中对齐包含上一步下一步按钮点击分别触发对应的方法 -->
<el-form-item style="text-align: center">
<el-button size="medium" @click="handlePrev"></el-button>
<el-button type="primary" size="medium" @click="handleNext"></el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
// @/api/productAttrCate fetchList fetchProductAttrCateList
import {fetchList as fetchProductAttrCateList} from '@/api/productAttrCate'
// @/api/productAttr fetchList fetchProductAttrList
import {fetchList as fetchProductAttrList} from '@/api/productAttr'
// SingleUpload
import SingleUpload from '@/api/SingleUpload'
// MultiUpload
import MultiUpload from '@/api/MultiUpload'
// Tinymce
import Tinymce from '@/api/Tinymce'
export default
name: "ProductAttrDetail",
components: {SingleUpload, MultiUpload, Tinymce},
props: {
// value
value: Object,
// isEdit false
isEdit: {
type: Boolean,
default: false
}
},
data() {
return {
// false
hasEditCreated: false,
//
productAttributeCategoryOptions: [],
//
selectProductAttr: [],
//
selectProductParam: [],
//
selectProductAttrPics: [],
//
addProductAttrValue: '',
// 'pc'
activeHtmlName: 'pc'
}
},
computed: {
// selectProductAttrPics
hasAttrPic() {
if (this.selectProductAttrPics.length < 1) {
return false;
}
return true;
},
// value
productId() {
return this.value.id;
},
// value
selectProductPics: {
get: function () {
let pics = [];
if (this.value.pic === undefined || this.value.pic == null || this.value.pic === '') {
return pics;
}
pics.push(this.value.pic);
if (this.value.albumPics === undefined || this.value.albumPics == null || this.value.albumPics === '') {
return pics;
}
let albumPics = this.value.albumPics.split(',');
for (let i = 0; i < albumPics.length; i++) {
pics.push(albumPics[i]);
}
return pics;
},
set: function (newValue) {
if (newValue == null || newValue.length === 0) {
this.value.pic = null;
this.value.albumPics = null;
} else {
this.value.pic = newValue[0];
this.value.albumPics = '';
if (newValue.length > 1) {
for (let i = 1; i < newValue.length; i++) {
this.value.albumPics += newValue[i];
if (i!== newValue.length - 1) {
this.value.albumPics += ',';
}
}
}
}
}
}
},
created() {
// getProductAttrCateList
this.getProductAttrCateList();
},
watch: {
productId: function (newValue) {
//
if (!this.isEdit) return;
//
if (this.hasEditCreated) return;
// undefinednull 0
if (newValue === undefined || newValue == null || newValue === 0) return;
// handleEditCreated
this.handleEditCreated();
}
},
methods:
handleEditCreated() {
// id id
if (this.value.productAttributeCategoryId!= null) {
this.handleProductAttrChange(this.value.productAttributeCategoryId);
}
// true
this.hasEditCreated = true;
},
getProductAttrCateList()

@ -0,0 +1,396 @@
<template> 
<el-card class="form-container" shadow="never">
<el-steps :active="active" finish-status="success" align-center>
<el-step title="填写商品信息"></el-step>
<el-step title="填写商品促销"></el-step>
<el-step title="填写商品属性"></el-step>
<el-step title="选择商品关联"></el-step>
</el-steps>
<product-info-detail
v-show="showStatus[0]"
v-model="productParam"
:is-edit="isEdit"
@nextStep="nextStep">
</product-info-detail>
<product-sale-detail
v-show="showStatus[1]"
v-model="productParam"
:is-edit="isEdit"
@nextStep="nextStep"
@prevStep="prevStep">
</product-sale-detail>
<product-attr-detail
v-show="showStatus[2]"
v-model="productParam"
:is-edit="isEdit"
@nextStep="nextStep"
@prevStep="prevStep">
</product-attr-detail>
<product-relation-detail
v-show="showStatus[3]"
v-model="productParam"
:is-edit="isEdit"
@prevStep="prevStep"
@finishCommit="finishCommit">
</product-relation-detail>
</el-card>
</template>
<script>
import ProductInfoDetail from './ProductInfoDetail';
import ProductSaleDetail from './ProductSaleDetail';
import ProductAttrDetail from './ProductAttrDetail';
import ProductRelationDetail from './ProductRelationDetail';
import {createProduct,getProduct,updateProduct} from '@/api/product';
const defaultProductParam = {
albumPics: '',
brandId: null,
brandName: '',
deleteStatus: 0,
description: '',
detailDesc: '',
detailHtml: '',
detailMobileHtml: '',
detailTitle: '',
feightTemplateId: 0,
flashPromotionCount: 0,
flashPromotionId: 0,
flashPromotionPrice: 0,
flashPromotionSort: 0,
giftPoint: 0,
giftGrowth: 0,
keywords: '',
lowStock: 0,
name: '',
newStatus: 0,
note: '',
originalPrice: 0,
pic: '',
//{memberLevelId: 0,memberPrice: 0,memberLevelName: null}
memberPriceList: [],
//
productFullReductionList: [{fullPrice: 0, reducePrice: 0}],
//
productLadderList: [{count: 0,discount: 0,price: 0}],
previewStatus: 0,
price: 0,
productAttributeCategoryId: null,
//{productAttributeId: 0, value: ''}
productAttributeValueList: [],
//sku{lowStock: 0, pic: '', price: 0, sale: 0, skuCode: '', spData: '', stock: 0}
skuStockList: [],
//{subjectId: 0}
subjectProductRelationList: [],
//{prefrenceAreaId: 0}
prefrenceAreaProductRelationList: [],
productCategoryId: null,
productCategoryName: '',
productSn: '',
promotionEndTime: '',
promotionPerLimit: 0,
promotionPrice: null,
promotionStartTime: '',
promotionType: 0,
publishStatus: 0,
recommandStatus: 0,
sale: 0,
serviceIds: '',
sort: 0,
stock: 0,
subTitle: '',
unit: '',
usePointLimit: 0,
verifyStatus: 0,
weight: 0
};
export default {
name: 'ProductDetail',
components: {ProductInfoDetail, ProductSaleDetail, ProductAttrDetail, ProductRelationDetail},
props: {
isEdit: {
type: Boolean,
default: false
}
},
data() {
return {
active: 0,
productParam: Object.assign({}, defaultProductParam),
showStatus: [true, false, false, false]
}
},
created(){
if(this.isEdit){
getProduct(this.$route.query.id).then(response=>{
this.productParam=response.data;
});
}
},
methods: {
hideAll() {
for (let i = 0; i < this.showStatus.length; i++) {
this.showStatus[i] = false;
}
},
prevStep() {
if (this.active > 0 && this.active < this.showStatus.length) {
this.active--;
this.hideAll();
this.showStatus[this.active] = true;
}
},
nextStep() {
if (this.active < this.showStatus.length - 1) {
this.active++;
this.hideAll();
this.showStatus[this.active] = true;
}
},
finishCommit(isEdit) {
this.$confirm('是否要提交该产品', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
if(isEdit){
updateProduct(this.$route.query.id,this.productParam).then(response=>{
this.$message({
type: 'success',
message: '提交成功',
duration:1000
});
this.$router.back();
});
}else{
createProduct(this.productParam).then(response=>{
this.$message({
type: 'success',
message: '提交成功',
duration:1000
});
location.reload();
});
}
})
}
}
}
</script>
<style>
.form-container {
width: 960px;
}
.form-inner-container {
width: 800px;
}
</style>
#abc
<template>
<!-- 使用Element UI的el-card组件创建一个卡片式容器设置类名为form-container阴影效果为无用于对内部内容进行布局包裹 -->
<el-card class="form-container" shadow="never">
<!-- 使用Element UI的el-steps步骤条组件绑定:active属性用于控制当前激活的步骤设置完成状态的样式为success并且让步骤标题居中对齐 -->
<el-steps :active="active" finish-status="success" align-center>
<!-- 定义一个步骤标题为填写商品信息具体内容由对应的组件product-info-detail来填充 -->
<el-step title="填写商品信息"></el-step>
<!-- 定义一个步骤标题为填写商品促销具体内容由对应的组件product-sale-detail来填充 -->
<el-step title="填写商品促销"></el-step>
<!-- 定义一个步骤标题为填写商品属性具体内容由对应的组件product-attr-detail来填充 -->
<el-step title="填写商品属性"></el-step>
<!-- 定义一个步骤标题为选择商品关联具体内容由对应的组件product-relation-detail来填充 -->
<el-step title="选择商品关联"></el-step>
</el-steps>
<!-- 使用product-info-detail组件通过v-show指令根据showStatus数组中对应索引的值来控制显示隐藏绑定v-model用于双向数据绑定传递is-edit属性并且监听nextStep事件 -->
<product-info-detail
v-show="showStatus[0]"
v-model="productParam"
:is-edit="isEdit"
@nextStep="nextStep">
</product-info-detail>
<!-- 使用product-sale-detail组件通过v-show指令根据showStatus数组中对应索引的值来控制显示隐藏绑定v-model用于双向数据绑定传递is-edit属性并且监听nextStep和prevStep事件 -->
<product-sale-detail
v-show="showStatus[1]"
v-model="productParam"
:is-edit="isEdit"
@nextStep="nextStep"
@prevStep="prevStep">
</product-sale-detail>
<!-- 使用product-attr-detail组件通过v-show指令根据showStatus数组中对应索引的值来控制显示隐藏绑定v-model用于双向数据绑定传递is-edit属性并且监听nextStep和prevStep事件 -->
<product-attr-detail
v-show="showStatus[2]"
v-model="productParam"
:is-edit="isEdit"
@nextStep="nextStep"
@prevStep="prevStep">
</product-attr-detail>
<!-- 使用product-relation-detail组件通过v-show指令根据showStatus数组中对应索引的值来控制显示隐藏绑定v-model用于双向数据绑定传递is-edit属性并且监听prevStep和finishCommit事件 -->
<product-relation-detail
v-show="showStatus[3]"
v-model="productParam"
:is-edit="isEdit"
@prevStep="prevStep"
@finishCommit="finishCommit">
</product-relation-detail>
</el-card>
</template>
<script>
// ProductInfoDetailVue
import ProductInfoDetail from './ProductInfoDetail';
// ProductSaleDetailVue
import ProductSaleDetail from './ProductSaleDetail';
// ProductAttrDetailVue
import ProductAttrDetail from './ProductAttrDetail';
// ProductRelationDetailVue
import ProductRelationDetail from './ProductRelationDetail';
// @/api/productcreateProductgetProductupdateProduct
import {createProduct, getProduct, updateProduct} from '@/api/product';
//
const defaultProductParam = {
albumPics: '',
brandId: null,
brandName: '',
deleteStatus: 0,
description: '',
detailDesc: '',
detailHtml: '',
detailMobileHtml: '',
detailTitle: '',
feightTemplateId: 0,
flashPromotionCount: 0,
flashPromotionId: 0,
flashPromotionPrice: 0,
flashPromotionSort: 0,
giftPoint: 0,
giftGrowth: 0,
keywords: '',
lowStock: 0,
name: '',
newStatus: 0,
note: '',
originalPrice: 0,
pic: '',
// ID
memberPriceList: [],
//
productFullReductionList: [{fullPrice: 0, reducePrice: 0}],
//
productLadderList: [{count: 0, discount: 0, price: 0}],
previewStatus: 0,
price: 0,
productAttributeCategoryId: null,
// ID
productAttributeValueList: [],
// SKUSKU
skuStockList: [],
// ID
subjectProductRelationList: [],
// ID
prefrenceAreaProductRelationList: [],
productCategoryId: null,
productCategoryName: '',
productSn: '',
promotionEndTime: '',
promotionPerLimit: 0,
promotionPrice: null,
promotionStartTime: '',
promotionType: 0,
publishStatus: 0,
recommandStatus: 0,
sale: 0,
serviceIds: '',
sort: 0,
stock: 0,
subTitle: '',
unit: '',
usePointLimit: 0,
verifyStatus: 0,
weight: 0
};
export default {
name: 'ProductDetail',
components: {ProductInfoDetail, ProductSaleDetail, ProductAttrDetail, ProductRelationDetail},
props: {
// isEditfalse
isEdit: {
type: Boolean,
default: false
}
},
data() {
return {
// 0
active: 0,
// Object.assign
productParam: Object.assign({}, defaultProductParam),
// true
showStatus: [true, false, false, false]
}
},
created() {
// isEdittruegetProductIDproductParam
if (this.isEdit) {
getProduct(this.$route.query.id).then(response => {
this.productParam = response.data;
});
}
},
methods: {
hideAll() {
// showStatusfalse
for (let i = 0; i < this.showStatus.length; i++) {
this.showStatus[i] = false;
}
},
prevStep() {
// 0退1hideAll
if (this.active > 0 && this.active < this.showStatus.length) {
this.active--;
this.hideAll();
this.showStatus[this.active] = true;
}
},
nextStep() {
// 11hideAll
if (this.active < this.showStatus.length - 1) {
this.active++;
this.hideAll();
this.showStatus[this.active] = true;
}
},
finishCommit(isEdit) {
// isEdit
this.$confirm('是否要提交该产品', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
if (isEdit) {
// isEdittrueupdateProductID
updateProduct(this.$route.query.id, this.productParam).then(response => {
this.$message({
type: 'success',
message: '提交成功',
duration: 1000
});
this.$router.back();
});
} else {
// createProduct
createProduct(this.productParam).then(response => {
this.$message({
type: 'success',
message: '提交成功',
duration: 1000
});
location.reload();
});
}
})
}
}
}
</script>

@ -0,0 +1,384 @@
<template>
<div style="margin-top: 50px">
<el-form :model="value" :rules="rules" ref="productInfoForm" label-width="120px" class="form-inner-container" size="small">
<el-form-item label="商品分类:" prop="productCategoryId">
<el-cascader
v-model="selectProductCateValue"
:options="productCateOptions">
</el-cascader>
</el-form-item>
<el-form-item label="商品名称:" prop="name">
<el-input v-model="value.name"></el-input>
</el-form-item>
<el-form-item label="副标题:" prop="subTitle">
<el-input v-model="value.subTitle"></el-input>
</el-form-item>
<el-form-item label="商品品牌:" prop="brandId">
<el-select
v-model="value.brandId"
@change="handleBrandChange"
placeholder="请选择品牌">
<el-option
v-for="item in brandOptions"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="商品介绍:">
<el-input
:autoSize="true"
v-model="value.description"
type="textarea"
placeholder="请输入内容"></el-input>
</el-form-item>
<el-form-item label="商品货号:">
<el-input v-model="value.productSn"></el-input>
</el-form-item>
<el-form-item label="商品售价:">
<el-input v-model="value.price"></el-input>
</el-form-item>
<el-form-item label="市场价:">
<el-input v-model="value.originalPrice"></el-input>
</el-form-item>
<el-form-item label="商品库存:">
<el-input v-model="value.stock"></el-input>
</el-form-item>
<el-form-item label="计量单位:">
<el-input v-model="value.unit"></el-input>
</el-form-item>
<el-form-item label="商品重量:">
<el-input v-model="value.weight" style="width: 300px"></el-input>
<span style="margin-left: 20px"></span>
</el-form-item>
<el-form-item label="排序">
<el-input v-model="value.sort"></el-input>
</el-form-item>
<el-form-item style="text-align: center">
<el-button type="primary" size="medium" @click="handleNext('productInfoForm')"></el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
import {fetchListWithChildren} from '@/api/productCate'
import {fetchList as fetchBrandList} from '@/api/brand'
import {getProduct} from '@/api/product';
export default {
name: "ProductInfoDetail",
props: {
value: Object,
isEdit: {
type: Boolean,
default: false
}
},
data() {
return {
hasEditCreated:false,
//
selectProductCateValue: [],
productCateOptions: [],
brandOptions: [],
rules: {
name: [
{required: true, message: '请输入商品名称', trigger: 'blur'},
{min: 2, max: 140, message: '长度在 2 到 140 个字符', trigger: 'blur'}
],
subTitle: [{required: true, message: '请输入商品副标题', trigger: 'blur'}],
productCategoryId: [{required: true, message: '请选择商品分类', trigger: 'blur'}],
brandId: [{required: true, message: '请选择商品品牌', trigger: 'blur'}],
description: [{required: true, message: '请输入商品介绍', trigger: 'blur'}],
requiredProp: [{required: true, message: '该项为必填项', trigger: 'blur'}]
}
};
},
created() {
this.getProductCateList();
this.getBrandList();
},
computed:{
//
productId(){
return this.value.id;
}
},
watch: {
productId:function(newValue){
if(!this.isEdit)return;
if(this.hasEditCreated)return;
if(newValue===undefined||newValue==null||newValue===0)return;
this.handleEditCreated();
},
selectProductCateValue: function (newValue) {
if (newValue != null && newValue.length === 2) {
this.value.productCategoryId = newValue[1];
this.value.productCategoryName= this.getCateNameById(this.value.productCategoryId);
} else {
this.value.productCategoryId = null;
this.value.productCategoryName=null;
}
}
},
methods: {
//
handleEditCreated(){
if(this.value.productCategoryId!=null){
this.selectProductCateValue.push(this.value.cateParentId);
this.selectProductCateValue.push(this.value.productCategoryId);
}
this.hasEditCreated=true;
},
getProductCateList() {
fetchListWithChildren().then(response => {
let list = response.data;
this.productCateOptions = [];
for (let i = 0; i < list.length; i++) {
let children = [];
if (list[i].children != null && list[i].children.length > 0) {
for (let j = 0; j < list[i].children.length; j++) {
children.push({label: list[i].children[j].name, value: list[i].children[j].id});
}
}
this.productCateOptions.push({label: list[i].name, value: list[i].id, children: children});
}
});
},
getBrandList() {
fetchBrandList({pageNum: 1, pageSize: 100}).then(response => {
this.brandOptions = [];
let brandList = response.data.list;
for (let i = 0; i < brandList.length; i++) {
this.brandOptions.push({label: brandList[i].name, value: brandList[i].id});
}
});
},
getCateNameById(id){
let name=null;
for(let i=0;i<this.productCateOptions.length;i++){
for(let j=0;j<this.productCateOptions[i].children.length;j++){
if(this.productCateOptions[i].children[j].value===id){
name=this.productCateOptions[i].children[j].label;
return name;
}
}
}
return name;
},
handleNext(formName){
this.$refs[formName].validate((valid) => {
if (valid) {
this.$emit('nextStep');
} else {
this.$message({
message: '验证失败',
type: 'error',
duration:1000
});
return false;
}
});
},
handleBrandChange(val) {
let brandName = '';
for (let i = 0; i < this.brandOptions.length; i++) {
if (this.brandOptions[i].value === val) {
brandName = this.brandOptions[i].label;
break;
}
}
this.value.brandName = brandName;
}
}
}
</script>
<style scoped>
</style>
#abc
<template>
<!-- 外层的 div 元素设置了距离顶部 50px margin用于在页面布局上与其他元素产生间隔 -->
<div style="margin-top: 50px">
<!-- el-form 表单组件用于收集和验证商品相关信息绑定数据模型为 value关联验证规则 rules设置了表单引用名标签宽度类名以及尺寸为小 -->
<el-form :model="value" :rules="rules" ref="productInfoForm" label-width="120px" class="form-inner-container" size="small">
<!-- 表单项标签显示为商品分类通过 prop 属性关联对应的验证规则用于选择商品分类 -->
<el-form-item label="商品分类:" prop="productCategoryId">
<!-- el-cascader 级联选择器组件绑定 v-model selectProductCateValue其选项数据由 productCateOptions 提供用于选择多级的商品分类 -->
<el-cascader
v-model="selectProductCateValue"
:options="productCateOptions">
</el-cascader>
</el-form-item>
<!-- 表单项标签显示为商品名称通过 prop 属性关联对应的验证规则内部使用输入框收集商品名称信息 -->
<el-form-item label="商品名称:" prop="name">
<el-input v-model="value.name"></el-input>
</el-form-item>
<!-- 表单项标签显示为副标题通过 prop 属性关联对应的验证规则内部使用输入框收集商品副标题信息 -->
<el-form-item label="副标题:" prop="subTitle">
<el-input v-model="value.subTitle"></el-input>
</el-form-item>
<!-- 表单项标签显示为商品品牌通过 prop 属性关联对应的验证规则内部使用下拉选择框选择商品品牌并且监听 change 事件 -->
<el-form-item label="商品品牌:" prop="brandId">
<el-select
v-model="value.brandId"
@change="handleBrandChange"
placeholder="请选择品牌">
<!-- 通过循环生成下拉选项每个选项的标签和值根据 brandOptions 数组中的元素来确定 -->
<el-option
v-for="item in brandOptions"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<!-- 表单项标签显示为商品介绍内部使用可自动调整大小的文本域输入框收集商品介绍信息 -->
<el-form-item label="商品介绍:">
<el-input
:autoSize="true"
v-model="value.description"
type="textarea"
placeholder="请输入内容"></el-input>
</el-form-item>
<!-- 表单项标签显示为商品货号内部使用输入框收集商品货号信息 -->
<el-form-item label="商品货号:">
<el-input v-model="value.productSn"></el-input>
</el-form-item>
<!-- 表单项标签显示为商品售价内部使用输入框收集商品售价信息 -->
<el-form-item label="商品售价:">
<el-input v-model="value.price"></el-input>
</el-form-item>
<!-- 表单项标签显示为市场价内部使用输入框收集商品市场价信息 -->
<el-form-item label="市场价:">
<el-input v-model="value.originalPrice"></el-input>
</el-form-item>
<!-- 表单项标签显示为商品库存内部使用输入框收集商品库存信息 -->
<el-form-item label="商品库存:">
<el-input v-model="value.stock"></el-input>
</el-form-item>
<!-- 表单项标签显示为计量单位内部使用输入框收集商品计量单位信息 -->
<el-form-item label="计量单位:">
<el-input v-model="value.unit"></el-input>
</el-form-item>
<!-- 表单项标签显示为商品重量内部使用输入框收集商品重量信息并在输入框右侧显示作为单位提示 -->
<el-form-item label="商品重量:">
<el-input v-model="value.weight" style="width: 300px"></el-input>
<span style="margin-left: 20px"></span>
</el-form-item>
<!-- 表单项标签显示为排序内部使用输入框收集商品排序相关信息 -->
<el-form-item label="排序">
<el-input v-model="value.sort"></el-input>
</el-form-item>
<!-- 表单项用于放置操作按钮设置文本居中对齐包含一个下一步按钮点击时触发 handleNext 方法并传入当前表单名称用于进入下一个填写商品促销的步骤 -->
<el-form-item style="text-align: center">
<el-button type="primary" size="medium" @click="handleNext('productInfoForm')"></el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
// @/api/productCate fetchListWithChildren
import {fetchListWithChildren} from '@/api/productCate'
// @/api/brand fetchList fetchBrandList
import {fetchList as fetchBrandList} from '@/api/brand'
// @/api/product getProduct 使
import {getProduct} from '@/api/product';
export default
name: "ProductInfoDetail",
props: {
// value 便
value: Object,
// isEdit false
isEdit: {
type: Boolean,
default: false
}
},
data() {
return {
// false
hasEditCreated: false,
//
selectProductCateValue: [],
// ID
productCateOptions: [],
// ID
brandOptions: [],
//
rules: {
name: [
//
{required: true, message: '请输入商品名称', trigger: 'blur'},
// 2 140 2 140
{min: 2, max: 140, message: '长度在 2 到 140 个字符', trigger: 'blur'}
],
subTitle: [
//
{required: true, message: '请输入商品副标题', trigger: 'blur'}
],
productCategoryId: [
//
{required: true, message: '请选择商品分类', trigger: 'blur'}
],
brandId: [
//
{required: true, message: '请选择商品品牌', trigger: 'blur'}
],
description: [
//
{required: true, message: '请输入商品介绍', trigger: 'blur'}
],
requiredProp: [
//
{required: true, message: '该项为必填项', trigger: 'blur'}
]
}
};
},
created() {
// getProductCateList getBrandList
this.getProductCateList();
this.getBrandList();
},
computed: {
// value id undefined
productId() {
return this.value.id;
}
},
watch: {
productId: function (newValue) {
//
if (!this.isEdit) return;
//
if (this.hasEditCreated) return;
// undefinednull 0
if (newValue === undefined || newValue == null || newValue === 0) return;
// handleEditCreated
this.handleEditCreated();
},
selectProductCateValue: function (newValue) {
// null 2
if (newValue!= null && newValue.length === 2) {
// ID ID
this.value.productCategoryId = newValue[1];
// getCateNameById
this.value.productCategoryName = this.getCateNameById(this.value.productCategoryId);
} else {
// ID null
this.value.productCategoryId = null;
this.value.productCategoryName = null;
}
}
},
methods:
//
handleEditCreated()
if (this.value.productCategoryId!= null)
//

@ -0,0 +1,310 @@
<template>
<div style="margin-top: 50px">
<el-form :model="value"
ref="productRelationForm"
label-width="120px"
class="form-inner-container"
size="small">
<el-form-item label="关联专题:">
<el-transfer
style="display: inline-block"
filterable
:filter-method="filterMethod"
filter-placeholder="请输入专题名称"
v-model="selectSubject"
:titles="subjectTitles"
:data="subjectList">
</el-transfer>
</el-form-item>
<el-form-item label="关联优选:">
<el-transfer
style="display: inline-block"
filterable
:filter-method="filterMethod"
filter-placeholder="请输入优选名称"
v-model="selectPrefrenceArea"
:titles="prefrenceAreaTitles"
:data="prefrenceAreaList">
</el-transfer>
</el-form-item>
<el-form-item style="text-align: center">
<el-button size="medium" @click="handlePrev"></el-button>
<el-button type="primary" size="medium" @click="handleFinishCommit"></el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
import {fetchListAll as fetchSubjectList} from '@/api/subject'
import {fetchList as fetchPrefrenceAreaList} from '@/api/prefrenceArea'
export default {
name: "ProductRelationDetail",
props: {
value: Object,
isEdit: {
type: Boolean,
default: false
}
},
data() {
return {
//
subjectList: [],
//
subjectTitles: ['待选择', '已选择'],
//
prefrenceAreaList: [],
//
prefrenceAreaTitles: ['待选择', '已选择']
};
},
created() {
this.getSubjectList();
this.getPrefrenceAreaList();
},
computed:{
//
selectSubject:{
get:function () {
let subjects =[];
if(this.value.subjectProductRelationList==null||this.value.subjectProductRelationList.length<=0){
return subjects;
}
for(let i=0;i<this.value.subjectProductRelationList.length;i++){
subjects.push(this.value.subjectProductRelationList[i].subjectId);
}
return subjects;
},
set:function (newValue) {
this.value.subjectProductRelationList=[];
for(let i=0;i<newValue.length;i++){
this.value.subjectProductRelationList.push({subjectId:newValue[i]});
}
}
},
//
selectPrefrenceArea:{
get:function () {
let prefrenceAreas =[];
if(this.value.prefrenceAreaProductRelationList==null||this.value.prefrenceAreaProductRelationList.length<=0){
return prefrenceAreas;
}
for(let i=0;i<this.value.prefrenceAreaProductRelationList.length;i++){
prefrenceAreas.push(this.value.prefrenceAreaProductRelationList[i].prefrenceAreaId);
}
return prefrenceAreas;
},
set:function (newValue) {
this.value.prefrenceAreaProductRelationList=[];
for(let i=0;i<newValue.length;i++){
this.value.prefrenceAreaProductRelationList.push({prefrenceAreaId:newValue[i]});
}
}
}
},
methods: {
filterMethod(query, item) {
return item.label.indexOf(query) > -1;
},
getSubjectList() {
fetchSubjectList().then(response => {
let list = response.data;
for (let i = 0; i < list.length; i++) {
this.subjectList.push({
label: list[i].title,
key: list[i].id
});
}
});
},
getPrefrenceAreaList() {
fetchPrefrenceAreaList().then(response=>{
let list = response.data;
for (let i = 0; i < list.length; i++) {
this.prefrenceAreaList.push({
label: list[i].name,
key: list[i].id
});
}
});
},
handlePrev(){
this.$emit('prevStep')
},
handleFinishCommit(){
this.$emit('finishCommit',this.isEdit);
}
}
}
</script>
<style scoped>
</style>
#abc
<template>
<!-- 外层的 div 元素设置了距离顶部 50px margin用于在页面布局上与其他元素产生间隔 -->
<div style="margin-top: 50px">
<!-- el-form 表单组件用于收集和展示商品关联相关信息绑定数据模型为 value设置了表单引用名标签宽度类名以及尺寸为小 -->
<el-form :model="value"
ref="productRelationForm"
label-width="120px"
class="form-inner-container"
size="small">
<!-- 表单项标签显示为关联专题用于选择商品关联的专题信息 -->
<el-form-item label="关联专题:">
<!-- el-transfer 穿梭框组件设置为内联块级元素显示可进行过滤操作绑定过滤方法设置过滤占位提示文字双向绑定选中的数据指定左右标题以及数据源 -->
<el-transfer
style="display: inline-block"
filterable
:filter-method="filterMethod"
filter-placeholder="请输入专题名称"
v-model="selectSubject"
:titles="subjectTitles"
:data="subjectList">
</el-transfer>
</el-form-item>
<!-- 表单项标签显示为关联优选用于选择商品关联的优选信息 -->
<el-form-item label="关联优选:">
<!-- el-transfer 穿梭框组件设置为内联块级元素显示可进行过滤操作绑定过滤方法设置过滤占位提示文字双向绑定选中的数据指定左右标题以及数据源 -->
<el-transfer
style="display: inline-block"
filterable
:filter-method="filterMethod"
filter-placeholder="请输入优选名称"
v-model="selectPrefrenceArea"
:titles="prefrenceAreaTitles"
:data="prefrenceAreaList">
</el-transfer>
</el-form-item>
<!-- 表单项用于放置操作按钮设置文本居中对齐包含上一步完成提交商品按钮点击分别触发对应的方法 -->
<el-form-item style="text-align: center">
<el-button size="medium" @click="handlePrev"></el-button>
<el-button type="primary" size="medium" @click="handleFinishCommit"></el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
// @/api/subject fetchListAll fetchSubjectList
import {fetchListAll as fetchSubjectList} from '@/api/subject'
// @/api/prefrenceArea fetchList fetchPrefrenceAreaList
import {fetchList as fetchPrefrenceAreaList} from '@/api/prefrenceArea'
export default {
name: "ProductRelationDetail",
props: {
// value 便
value: Object,
// isEdit false
isEdit: {
type: Boolean,
default: false
}
},
data() {
return {
// ID
subjectList: [],
// 穿穿
subjectTitles: ['待选择', '已选择'],
// ID
prefrenceAreaList: [],
// 穿穿
prefrenceAreaTitles: ['待选择', '已选择']
};
},
created() {
// getSubjectList getPrefrenceAreaList
this.getSubjectList();
this.getPrefrenceAreaList();
},
computed: {
// value subjectProductRelationList ID
selectSubject: {
get: function () {
let subjects = [];
if (this.value.subjectProductRelationList == null || this.value.subjectProductRelationList.length <= 0) {
return subjects;
}
for (let i = 0; i < this.value.subjectProductRelationList.length; i++) {
subjects.push(this.value.subjectProductRelationList[i].subjectId);
}
return subjects;
},
set: function (newValue) {
// value subjectProductRelationList ID
this.value.subjectProductRelationList = [];
for (let i = 0; i < newValue.length; i++) {
this.value.subjectProductRelationList.push({subjectId: newValue[i]});
}
}
},
// value prefrenceAreaProductRelationList ID
selectPrefrenceArea: {
get: function () {
let prefrenceAreas = [];
if (this.value.prefrenceAreaProductRelationList == null || this.value.prefrenceAreaProductRelationList.length <= 0) {
return prefrenceAreas;
}
for (let i = 0; i < this.value.prefrenceAreaProductRelationList.length; i++) {
prefrenceAreas.push(this.value.prefrenceAreaProductRelationList[i].prefrenceAreaId);
}
return prefrenceAreas;
},
set: function (newValue) {
// value prefrenceAreaProductRelationList ID
this.value.prefrenceAreaProductRelationList = [];
for (let i = 0; i < newValue.length; i++) {
this.value.prefrenceAreaProductRelationList.push({prefrenceAreaId: newValue[i]});
}
}
}
},
methods: {
// el-transfer 穿queryitem
filterMethod(query, item) {
return item.label.indexOf(query) > -1;
},
getSubjectList() {
// fetchSubjectList 穿使
fetchSubjectList().then(response => {
let list = response.data;
for (let i = 0; i < list.length; i++) {
this.subjectList.push({
label: list[i].title,
key: list[i].id
});
}
});
},
getPrefrenceAreaList() {
// fetchPrefrenceAreaList 穿使
fetchPrefrenceAreaList().then(response => {
let list = response.data;
for (let i = 0; i < list.length; i++) {
this.prefrenceAreaList.push({
label: list[i].name,
key: list[i].id
});
}
});
},
handlePrev() {
// prevStep 退
this.$emit('prevStep')
},
handleFinishCommit() {
// finishCommit isEdit
this.$emit('finishCommit', this.isEdit);
}
}
}
</script>
<style scoped>
/* 这里的样式部分设置了 scoped 属性,表示样式仅作用于当前组件内部的元素,目前为空,可根据组件的设计需求添加具体的样式规则,比如对表单内各元素、穿梭框等的样式调整,使其在页面中呈现出符合设计预期的外观效果 */
</style>

@ -0,0 +1,601 @@
<template>
<div style="margin-top: 50px">
<el-form :model="value" ref="productSaleForm" label-width="120px" class="form-inner-container" size="small">
<el-form-item label="赠送积分:">
<el-input v-model="value.giftPoint"></el-input>
</el-form-item>
<el-form-item label="赠送成长值:">
<el-input v-model="value.giftGrowth"></el-input>
</el-form-item>
<el-form-item label="积分购买限制:">
<el-input v-model="value.usePointLimit"></el-input>
</el-form-item>
<el-form-item label="预告商品:">
<el-switch
v-model="value.previewStatus"
:active-value="1"
:inactive-value="0">
</el-switch>
</el-form-item>
<el-form-item label="商品上架:">
<el-switch
v-model="value.publishStatus"
:active-value="1"
:inactive-value="0">
</el-switch>
</el-form-item>
<el-form-item label="商品推荐:">
<span style="margin-right: 10px">新品</span>
<el-switch
v-model="value.newStatus"
:active-value="1"
:inactive-value="0">
</el-switch>
<span style="margin-left: 10px;margin-right: 10px">推荐</span>
<el-switch
v-model="value.recommandStatus"
:active-value="1"
:inactive-value="0">
</el-switch>
</el-form-item>
<el-form-item label="服务保证:">
<el-checkbox-group v-model="selectServiceList">
<el-checkbox :label="1">无忧退货</el-checkbox>
<el-checkbox :label="2">快速退款</el-checkbox>
<el-checkbox :label="3">免费包邮</el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="详细页标题:">
<el-input v-model="value.detailTitle"></el-input>
</el-form-item>
<el-form-item label="详细页描述:">
<el-input v-model="value.detailDesc"></el-input>
</el-form-item>
<el-form-item label="商品关键字:">
<el-input v-model="value.keywords"></el-input>
</el-form-item>
<el-form-item label="商品备注:">
<el-input v-model="value.note" type="textarea" :autoSize="true"></el-input>
</el-form-item>
<el-form-item label="选择优惠方式:">
<el-radio-group v-model="value.promotionType" size="small">
<el-radio-button :label="0">无优惠</el-radio-button>
<el-radio-button :label="1">特惠促销</el-radio-button>
<el-radio-button :label="2">会员价格</el-radio-button>
<el-radio-button :label="3">阶梯价格</el-radio-button>
<el-radio-button :label="4">满减价格</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item v-show="value.promotionType===1">
<div>
开始时间
<el-date-picker
v-model="value.promotionStartTime"
type="datetime"
:picker-options="pickerOptions1"
placeholder="选择开始时间">
</el-date-picker>
</div>
<div class="littleMargin">
结束时间
<el-date-picker
v-model="value.promotionEndTime"
type="datetime"
:picker-options="pickerOptions1"
placeholder="选择结束时间">
</el-date-picker>
</div>
<div class="littleMargin">
促销价格
<el-input style="width: 220px" v-model="value.promotionPrice" placeholder="输入促销价格"></el-input>
</div>
</el-form-item>
<el-form-item v-show="value.promotionType===2">
<div v-for="(item, index) in value.memberPriceList" :class="{littleMargin:index!==0}">
{{item.memberLevelName}}
<el-input v-model="item.memberPrice" style="width: 200px"></el-input>
</div>
</el-form-item>
<el-form-item v-show="value.promotionType===3">
<el-table :data="value.productLadderList"
style="width: 80%" border>
<el-table-column
label="数量"
align="center"
width="120">
<template slot-scope="scope">
<el-input v-model="scope.row.count"></el-input>
</template>
</el-table-column>
<el-table-column
label="折扣"
align="center"
width="120">
<template slot-scope="scope">
<el-input v-model="scope.row.discount"></el-input>
</template>
</el-table-column>
<el-table-column
align="center"
label="操作">
<template slot-scope="scope">
<el-button type="text" @click="handleRemoveProductLadder(scope.$index, scope.row)">删除</el-button>
<el-button type="text" @click="handleAddProductLadder(scope.$index, scope.row)">添加</el-button>
</template>
</el-table-column>
</el-table>
</el-form-item>
<el-form-item v-show="value.promotionType===4">
<el-table :data="value.productFullReductionList"
style="width: 80%" border>
<el-table-column
label="满"
align="center"
width="120">
<template slot-scope="scope">
<el-input v-model="scope.row.fullPrice"></el-input>
</template>
</el-table-column>
<el-table-column
label="立减"
align="center"
width="120">
<template slot-scope="scope">
<el-input v-model="scope.row.reducePrice"></el-input>
</template>
</el-table-column>
<el-table-column
align="center"
label="操作">
<template slot-scope="scope">
<el-button type="text" @click="handleRemoveFullReduction(scope.$index, scope.row)">删除</el-button>
<el-button type="text" @click="handleAddFullReduction(scope.$index, scope.row)">添加</el-button>
</template>
</el-table-column>
</el-table>
</el-form-item>
<el-form-item style="text-align: center">
<el-button size="medium" @click="handlePrev"></el-button>
<el-button type="primary" size="medium" @click="handleNext"></el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
import {fetchList as fetchMemberLevelList} from '@/api/memberLevel'
export default {
name: "ProductSaleDetail",
props: {
value: Object,
isEdit: {
type: Boolean,
default: false
}
},
data() {
return {
//
pickerOptions1: {
disabledDate(time) {
return time.getTime() < Date.now();
}
}
}
},
created() {
if (this.isEdit) {
// this.handleEditCreated();
} else {
fetchMemberLevelList({defaultStatus: 0}).then(response => {
let memberPriceList = [];
for (let i = 0; i < response.data.length; i++) {
let item = response.data[i];
memberPriceList.push({memberLevelId: item.id, memberLevelName: item.name})
}
this.value.memberPriceList = memberPriceList;
});
}
},
computed: {
//
selectServiceList: {
get() {
let list = [];
if (this.value.serviceIds === undefined || this.value.serviceIds == null || this.value.serviceIds === '') return list;
let ids = this.value.serviceIds.split(',');
for (let i = 0; i < ids.length; i++) {
list.push(Number(ids[i]));
}
return list;
},
set(newValue) {
let serviceIds = '';
if (newValue != null && newValue.length > 0) {
for (let i = 0; i < newValue.length; i++) {
serviceIds += newValue[i] + ',';
}
if (serviceIds.endsWith(',')) {
serviceIds = serviceIds.substr(0, serviceIds.length - 1)
}
this.value.serviceIds = serviceIds;
} else {
this.value.serviceIds = null;
}
}
}
},
methods: {
handleEditCreated() {
let ids = this.value.serviceIds.split(',');
console.log('handleEditCreated', ids);
for (let i = 0; i < ids.length; i++) {
this.selectServiceList.push(Number(ids[i]));
}
},
handleRemoveProductLadder(index, row) {
let productLadderList = this.value.productLadderList;
if (productLadderList.length === 1) {
productLadderList.pop();
productLadderList.push({
count: 0,
discount: 0,
price: 0
})
} else {
productLadderList.splice(index, 1);
}
},
handleAddProductLadder(index, row) {
let productLadderList = this.value.productLadderList;
if (productLadderList.length < 3) {
productLadderList.push({
count: 0,
discount: 0,
price: 0
})
} else {
this.$message({
message: '最多只能添加三条',
type: 'warning'
});
}
},
handleRemoveFullReduction(index, row) {
let fullReductionList = this.value.productFullReductionList;
if (fullReductionList.length === 1) {
fullReductionList.pop();
fullReductionList.push({
fullPrice: 0,
reducePrice: 0
});
} else {
fullReductionList.splice(index, 1);
}
},
handleAddFullReduction(index, row) {
let fullReductionList = this.value.productFullReductionList;
if (fullReductionList.length < 3) {
fullReductionList.push({
fullPrice: 0,
reducePrice: 0
});
} else {
this.$message({
message: '最多只能添加三条',
type: 'warning'
});
}
},
handlePrev() {
this.$emit('prevStep')
},
handleNext() {
this.$emit('nextStep')
}
}
}
</script>
<style scoped>
.littleMargin {
margin-top: 10px;
}
</style>
#abc
<template>
<!-- 外层的 div 元素设置了距离顶部 50px margin用于在页面布局上与其他元素产生间隔 -->
<div style="margin-top: 50px">
<!-- el-form 表单组件用于收集和展示商品促销相关信息绑定数据模型为 value设置了表单引用名标签宽度类名以及尺寸为小 -->
<el-form :model="value" ref="productSaleForm" label-width="120px" class="form-inner-container" size="small">
<!-- 表单项标签显示为赠送积分内部使用输入框收集赠送积分的数量信息 -->
<el-form-item label="赠送积分:">
<el-input v-model="value.giftPoint"></el-input>
</el-form-item>
<!-- 表单项标签显示为赠送成长值内部使用输入框收集赠送成长值的数量信息 -->
<el-form-item label="赠送成长值:">
<el-input v-model="value.giftGrowth"></el-input>
</el-form-item>
<!-- 表单项标签显示为积分购买限制内部使用输入框收集积分购买限制的相关信息 -->
<el-form-item label="积分购买限制:">
<el-input v-model="value.usePointLimit"></el-input>
</el-form-item>
<!-- 表单项标签显示为预告商品内部使用 el-switch 开关组件来控制商品是否为预告状态通过设置不同的激活值和非激活值来表示开关的两种状态 -->
<el-form-item label="预告商品:">
<el-switch
v-model="value.previewStatus"
:active-value="1"
:inactive-value="0">
</el-switch>
</el-form-item>
<!-- 表单项标签显示为商品上架内部使用 el-switch 开关组件来控制商品是否上架通过设置不同的激活值和非激活值来表示开关的两种状态 -->
<el-form-item label="商品上架:">
<el-switch
v-model="value.publishStatus"
:active-value="1"
:inactive-value="0">
</el-switch>
</el-form-item>
<!-- 表单项标签显示为商品推荐包含多个开关组件分别用于控制商品是否为新品以及是否推荐通过设置不同的激活值和非激活值来表示开关的两种状态并在开关之间添加了文字提示间隔 -->
<el-form-item label="商品推荐:">
<span style="margin-right: 10px">新品</span>
<el-switch
v-model="value.newStatus"
:active-value="1"
:inactive-value="0">
</el-switch>
<span style="margin-left: 10px;margin-right: 10px">推荐</span>
<el-switch
v-model="value.recommandStatus"
:active-value="1"
:inactive-value="0">
</el-switch>
</el-form-item>
<!-- 表单项标签显示为服务保证内部使用 el-checkbox-group 复选框组组件提供了几个固定的服务选项供用户选择用于选择商品的服务保证内容 -->
<el-form-item label="服务保证:">
<el-checkbox-group v-model="selectServiceList">
<el-checkbox :label="1">无忧退货</el-checkbox>
<el-checkbox :label="2">快速退款</el-checkbox>
<el-checkbox :label="3">免费包邮</el-checkbox>
</el-checkbox-group>
</el-form-item>
<!-- 表单项标签显示为详细页标题内部使用输入框收集商品详细页标题信息 -->
<el-form-item label="详细页标题:">
<el-input v-model="value.detailTitle"></el-input>
</el-form-item>
<!-- 表单项标签显示为详细页描述内部使用输入框收集商品详细页描述信息 -->
<el-form-item label="详细页描述:">
<el-input v-model="value.detailDesc"></el-input>
</el-form-item>
<!-- 表单项标签显示为商品关键字内部使用输入框收集商品关键字信息 -->
<el-form-item label="商品关键字:">
<el-input v-model="value.keywords"></el-input>
</el-form-item>
<!-- 表单项标签显示为商品备注内部使用可自动调整大小的文本域输入框收集商品备注信息 -->
<el-form-item label="商品备注:">
<el-input v-model="value.note" type="textarea" :autoSize="true"></el-input>
</el-form-item>
<!-- 表单项标签显示为选择优惠方式内部使用 el-radio-group 单选框组组件提供了几种不同的优惠方式选项供用户选择用于确定商品采用的优惠方式 -->
<el-form-item label="选择优惠方式:">
<el-radio-group v-model="value.promotionType" size="small">
<el-radio-button :label="0">无优惠</el-radio-button>
<el-radio-button :label="1">特惠促销</el-radio-button>
<el-radio-button :label="2">会员价格</el-radio-button>
<el-radio-button :label="3">阶梯价格</el-radio-button>
<el-radio-button :label="4">满减价格</el-radio-button>
</el-radio-group>
</el-form-item>
<!-- 根据商品优惠方式promotionType的值判断是否显示当值为 1特惠促销时显示该部分内容用于设置特惠促销的相关时间和价格信息 -->
<el-form-item v-show="value.promotionType===1">
<div>
开始时间
<!-- el-date-picker 日期时间选择器组件绑定 v-model value.promotionStartTime设置选择器类型为 datetime关联日期禁用配置有占位提示文字用于选择特惠促销开始时间 -->
<el-date-picker
v-model="value.promotionStartTime"
type="datetime"
:picker-options="pickerOptions1"
placeholder="选择开始时间">
</el-date-picker>
</div>
<div class="littleMargin">
结束时间
<!-- el-date-picker 日期时间选择器组件绑定 v-model value.promotionEndTime设置选择器类型为 datetime关联日期禁用配置有占位提示文字用于选择特惠促销结束时间 -->
<el-date-picker
v-model="value.promotionEndTime"
type="datetime"
:picker-options="pickerOptions1"
placeholder="选择结束时间">
</el-date-picker>
</div>
<div class="littleMargin">
促销价格
<!-- 输入框组件设置宽度为 220px绑定 v-model value.promotionPrice有占位提示文字用于输入特惠促销价格 -->
<el-input style="width: 220px" v-model="value.promotionPrice" placeholder="输入促销价格"></el-input>
</div>
</el-form-item>
<!-- 根据商品优惠方式promotionType的值判断是否显示当值为 2会员价格时显示该部分内容通过循环展示每个会员等级对应的价格输入框用于设置不同会员等级的商品价格 -->
<el-form-item v-show="value.promotionType===2">
<div v-for="(item, index) in value.memberPriceList" :class="{littleMargin:index!==0}">
{{item.memberLevelName}}
<el-input v-model="item.memberPrice" style="width: 200px"></el-input>
</div>
</el-form-item>
<!-- 根据商品优惠方式promotionType的值判断是否显示当值为 3阶梯价格时显示该部分内容使用 el-table 表格组件展示和操作商品阶梯价格相关信息包含数量折扣等列以及添加删除操作按钮 -->
<el-form-item v-show="value.promotionType===3">
<el-table :data="value.productLadderList"
style="width: 80%" border>
<!-- 表格列标签显示为数量内容居中对齐宽度为 120px内部使用输入框展示和编辑阶梯价格对应的数量信息 -->
<el-table-column
label="数量"
align="center"
width="120">
<template slot-scope="scope">
<el-input v-model="scope.row.count"></el-input>
</template>
</el-table-column>
<!-- 表格列标签显示为折扣内容居中对齐宽度为 120px内部使用输入框展示和编辑阶梯价格对应的折扣信息 -->
<el-table-column
label="折扣"
align="center"
width="120">
<template slot-scope="scope">
<el-input v-model="scope.row.discount"></el-input>
</template>
</el-table-column>
<!-- 表格列标签显示为操作内容居中对齐内部包含删除和添加按钮点击可对当前行的阶梯价格数据进行相应操作 -->
<el-table-column
align="center"
label="操作">
<template slot-scope="scope">
<el-button type="text" @click="handleRemoveProductLadder(scope.$index, scope.row)">删除</el-button>
<el-button type="text" @click="handleAddProductLadder(scope.$index, scope.row)">添加</el-button>
</template>
</el-table-column>
</el-table>
</el-form-item>
<!-- 根据商品优惠方式promotionType的值判断是否显示当值为 4满减价格时显示该部分内容使用 el-table 表格组件展示和操作商品满减价格相关信息包含满减条件立减金额等列以及添加删除操作按钮 -->
<el-form-item v-show="value.promotionType===4">
<el-table :data="value.productFullReductionList"
style="width: 80%" border>
<!-- 表格列标签显示为内容居中对齐宽度为 120px内部使用输入框展示和编辑满减价格对应的满金额信息 -->
<el-table-column
label="满"
align="center"
width="120">
<template slot-scope="scope">
<el-input v-model="scope.row.fullPrice"></el-input>
</template>
</el-table-column>
<!-- 表格列标签显示为立减内容居中对齐宽度为 120px内部使用输入框展示和编辑满减价格对应的立减金额信息 -->
<el-table-column
label="立减"
align="center"
width="120">
<template slot-scope="scope">
<el-input v-model="scope.row.reducePrice"></el-input>
</template>
</el-table-column>
<!-- 表格列标签显示为操作内容居中对齐内部包含删除和添加按钮点击可对当前行的满减价格数据进行相应操作 -->
<el-table-column
align="center"
label="操作">
<template slot-scope="scope">
<el-button type="text" @click="handleRemoveFullReduction(scope.$index, scope.row)">删除</el-button>
<el-button type="text" @click="handleAddFullReduction(scope.$index, scope.row)">添加</el-button>
</template>
</el-table-column>
</el-table>
</el-form-item>
<!-- 表单项用于放置操作按钮设置文本居中对齐包含上一步下一步按钮点击分别触发对应的方法用于在商品信息填写流程中进行步骤切换 -->
<el-form-item style="text-align: center">
<el-button size="medium" @click="handlePrev"></el-button>
<el-button type="primary" size="medium" @click="handleNext"></el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
// @/api/memberLevel fetchList fetchMemberLevelList
import {fetchList as fetchMemberLevelList} from '@/api/memberLevel'
export default
name: "ProductSaleDetail",
props: {
// value 便
value: Object,
// isEdit false
isEdit: {
type: Boolean,
default: false
}
},
data() {
return {
// disabledDate
pickerOptions1: {
disabledDate(time) {
return time.getTime() < Date.now();
}
}
};
},
created() {
if (this.isEdit) {
// handleEditCreated
// this.handleEditCreated();
} else {
// fetchMemberLevelList ID value.memberPriceList
fetchMemberLevelList({defaultStatus: 0}).then(response => {
let memberPriceList = [];
for (let i = 0; i < response.data.length; i++) {
let item = response.data[i];
memberPriceList.push({memberLevelId: item.id, memberLevelName: item.name})
}
this.value.memberPriceList = memberPriceList;
});
}
},
computed: {
// value.serviceIds 便
selectServiceList: {
get() {
let list = [];
if (this.value.serviceIds === undefined || this.value.serviceIds == null || this.value.serviceIds === '') return list;
let ids = this.value.serviceIds.split(',');
for (let i = 0; i < ids.length; i++) {
list.push(Number(ids[i]));
}
return list;
},
set(newValue) {
let serviceIds = '';
if (newValue!= null && newValue.length > 0) {
for (let i = 0; i < newValue.length; i++) {
serviceIds += newValue[i] + ',';
}
if (serviceIds.endsWith(',')) {
serviceIds = serviceIds.substr(0, serviceIds.length - 1)
}
this.value.serviceIds = serviceIds;
} else {
this.value.serviceIds = null;
}
}
}
},
methods:
handleEditCreated() {
let ids = this.value.serviceIds.split(',');
console.log('handleEditCreated', ids);
for (let i = 0; i < ids.length; i++) {
this.selectServiceList.push(Number(ids[i]));
}
},
handleRemoveProductLadder(index, row) {
let productLadderList = this.value.productLadderList;
// 0
if (productLadderList.length === 1) {
productLadderList.pop();
productLadderList.push({
count: 0,
discount: 0,
price: 0
})
} else {
//
productLadderList.splice(index, 1);
}
},
handleAddProductLadder(index, row)
let productLadderList = this.value.productLadderList;
// 3 0
if (productLadderList.length < 3)
productLadderList.push
count: 0,
discount:

@ -0,0 +1,908 @@
<template> 
<div class="app-container">
<el-card class="filter-container" shadow="never">
<div>
<i class="el-icon-search"></i>
<span>筛选搜索</span>
<el-button
style="float: right"
@click="handleSearchList()"
type="primary"
size="small">
查询结果
</el-button>
<el-button
style="float: right;margin-right: 15px"
@click="handleResetSearch()"
size="small">
重置
</el-button>
</div>
<div style="margin-top: 15px">
<el-form :inline="true" :model="listQuery" size="small" label-width="140px">
<el-form-item label="输入搜索:">
<el-input style="width: 203px" v-model="listQuery.keyword" placeholder="商品名称"></el-input>
</el-form-item>
<el-form-item label="商品货号:">
<el-input style="width: 203px" v-model="listQuery.productSn" placeholder="商品货号"></el-input>
</el-form-item>
<el-form-item label="商品分类:">
<el-cascader
clearable
v-model="selectProductCateValue"
:options="productCateOptions">
</el-cascader>
</el-form-item>
<el-form-item label="商品品牌:">
<el-select v-model="listQuery.brandId" placeholder="请选择品牌" clearable>
<el-option
v-for="item in brandOptions"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="上架状态:">
<el-select v-model="listQuery.publishStatus" placeholder="全部" clearable>
<el-option
v-for="item in publishStatusOptions"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="审核状态:">
<el-select v-model="listQuery.verifyStatus" placeholder="全部" clearable>
<el-option
v-for="item in verifyStatusOptions"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
</el-form>
</div>
</el-card>
<el-card class="operate-container" shadow="never">
<i class="el-icon-tickets"></i>
<span>数据列表</span>
<el-button
class="btn-add"
@click="handleAddProduct()"
size="mini">
添加
</el-button>
</el-card>
<div class="table-container">
<el-table ref="productTable"
:data="list"
style="width: 100%"
@selection-change="handleSelectionChange"
v-loading="listLoading"
border>
<el-table-column type="selection" width="60" align="center"></el-table-column>
<el-table-column label="编号" width="100" align="center">
<template slot-scope="scope">{{scope.row.id}}</template>
</el-table-column>
<el-table-column label="商品图片" width="120" align="center">
<template slot-scope="scope"><img style="height: 80px" :src="scope.row.pic"></template>
</el-table-column>
<el-table-column label="商品名称" align="center">
<template slot-scope="scope">
<p>{{scope.row.name}}</p>
<p>品牌{{scope.row.brandName}}</p>
</template>
</el-table-column>
<el-table-column label="价格/货号" width="120" align="center">
<template slot-scope="scope">
<p>价格{{scope.row.price}}</p>
<p>货号{{scope.row.productSn}}</p>
</template>
</el-table-column>
<el-table-column label="标签" width="140" align="center">
<template slot-scope="scope">
<p>上架
<el-switch
@change="handlePublishStatusChange(scope.$index, scope.row)"
:active-value="1"
:inactive-value="0"
v-model="scope.row.publishStatus">
</el-switch>
</p>
<p>新品
<el-switch
@change="handleNewStatusChange(scope.$index, scope.row)"
:active-value="1"
:inactive-value="0"
v-model="scope.row.newStatus">
</el-switch>
</p>
<p>推荐
<el-switch
@change="handleRecommendStatusChange(scope.$index, scope.row)"
:active-value="1"
:inactive-value="0"
v-model="scope.row.recommandStatus">
</el-switch>
</p>
</template>
</el-table-column>
<el-table-column label="排序" width="100" align="center">
<template slot-scope="scope">{{scope.row.sort}}</template>
</el-table-column>
<el-table-column label="SKU库存" width="100" align="center">
<template slot-scope="scope">
<el-button type="primary" icon="el-icon-edit" @click="handleShowSkuEditDialog(scope.$index, scope.row)" circle></el-button>
</template>
</el-table-column>
<el-table-column label="销量" width="100" align="center">
<template slot-scope="scope">{{scope.row.sale}}</template>
</el-table-column>
<el-table-column label="审核状态" width="100" align="center">
<template slot-scope="scope">
<p>{{scope.row.verifyStatus | verifyStatusFilter}}</p>
<p>
<el-button
type="text"
@click="handleShowVerifyDetail(scope.$index, scope.row)">审核详情
</el-button>
</p>
</template>
</el-table-column>
<el-table-column label="操作" width="160" align="center">
<template slot-scope="scope">
<p>
<el-button
size="mini"
@click="handleShowProduct(scope.$index, scope.row)">查看
</el-button>
<el-button
size="mini"
@click="handleUpdateProduct(scope.$index, scope.row)">编辑
</el-button>
</p>
<p>
<el-button
size="mini"
@click="handleShowLog(scope.$index, scope.row)">日志
</el-button>
<el-button
size="mini"
type="danger"
@click="handleDelete(scope.$index, scope.row)">删除
</el-button>
</p>
</template>
</el-table-column>
</el-table>
</div>
<div class="batch-operate-container">
<el-select
size="small"
v-model="operateType" placeholder="批量操作">
<el-option
v-for="item in operates"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
<el-button
style="margin-left: 20px"
class="search-button"
@click="handleBatchOperate()"
type="primary"
size="small">
确定
</el-button>
</div>
<div class="pagination-container">
<el-pagination
background
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
layout="total, sizes,prev, pager, next,jumper"
:page-size="listQuery.pageSize"
:page-sizes="[5,10,15]"
:current-page.sync="listQuery.pageNum"
:total="total">
</el-pagination>
</div>
<el-dialog
title="编辑货品信息"
:visible.sync="editSkuInfo.dialogVisible"
width="40%">
<span>商品货号</span>
<span>{{editSkuInfo.productSn}}</span>
<el-input placeholder="按sku编号搜索" v-model="editSkuInfo.keyword" size="small" style="width: 50%;margin-left: 20px">
<el-button slot="append" icon="el-icon-search" @click="handleSearchEditSku"></el-button>
</el-input>
<el-table style="width: 100%;margin-top: 20px"
:data="editSkuInfo.stockList"
border>
<el-table-column
label="SKU编号"
align="center">
<template slot-scope="scope">
<el-input v-model="scope.row.skuCode"></el-input>
</template>
</el-table-column>
<el-table-column
v-for="(item,index) in editSkuInfo.productAttr"
:label="item.name"
:key="item.id"
align="center">
<template slot-scope="scope">
{{getProductSkuSp(scope.row,index)}}
</template>
</el-table-column>
<el-table-column
label="销售价格"
width="80"
align="center">
<template slot-scope="scope">
<el-input v-model="scope.row.price"></el-input>
</template>
</el-table-column>
<el-table-column
label="商品库存"
width="80"
align="center">
<template slot-scope="scope">
<el-input v-model="scope.row.stock"></el-input>
</template>
</el-table-column>
<el-table-column
label="库存预警值"
width="100"
align="center">
<template slot-scope="scope">
<el-input v-model="scope.row.lowStock"></el-input>
</template>
</el-table-column>
</el-table>
<span slot="footer" class="dialog-footer">
<el-button @click="editSkuInfo.dialogVisible = false"> </el-button>
<el-button type="primary" @click="handleEditSkuConfirm"> </el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import {
fetchList,
updateDeleteStatus,
updateNewStatus,
updateRecommendStatus,
updatePublishStatus
} from '@/api/product'
import {fetchList as fetchSkuStockList,update as updateSkuStockList} from '@/api/skuStock'
import {fetchList as fetchProductAttrList} from '@/api/productAttr'
import {fetchList as fetchBrandList} from '@/api/brand'
import {fetchListWithChildren} from '@/api/productCate'
const defaultListQuery = {
keyword: null,
pageNum: 1,
pageSize: 5,
publishStatus: null,
verifyStatus: null,
productSn: null,
productCategoryId: null,
brandId: null
};
export default {
name: "productList",
data() {
return {
editSkuInfo:{
dialogVisible:false,
productId:null,
productSn:'',
productAttributeCategoryId:null,
stockList:[],
productAttr:[],
keyword:null
},
operates: [
{
label: "商品上架",
value: "publishOn"
},
{
label: "商品下架",
value: "publishOff"
},
{
label: "设为推荐",
value: "recommendOn"
},
{
label: "取消推荐",
value: "recommendOff"
},
{
label: "设为新品",
value: "newOn"
},
{
label: "取消新品",
value: "newOff"
},
{
label: "转移到分类",
value: "transferCategory"
},
{
label: "移入回收站",
value: "recycle"
}
],
operateType: null,
listQuery: Object.assign({}, defaultListQuery),
list: null,
total: null,
listLoading: true,
selectProductCateValue: null,
multipleSelection: [],
productCateOptions: [],
brandOptions: [],
publishStatusOptions: [{
value: 1,
label: '上架'
}, {
value: 0,
label: '下架'
}],
verifyStatusOptions: [{
value: 1,
label: '审核通过'
}, {
value: 0,
label: '未审核'
}]
}
},
created() {
this.getList();
this.getBrandList();
this.getProductCateList();
},
watch: {
selectProductCateValue: function (newValue) {
if (newValue != null && newValue.length == 2) {
this.listQuery.productCategoryId = newValue[1];
} else {
this.listQuery.productCategoryId = null;
}
}
},
filters: {
verifyStatusFilter(value) {
if (value === 1) {
return '审核通过';
} else {
return '未审核';
}
}
},
methods: {
getProductSkuSp(row, index) {
let spData = JSON.parse(row.spData);
if(spData!=null&&index<spData.length){
return spData[index].value;
}else{
return null;
}
},
getList() {
this.listLoading = true;
fetchList(this.listQuery).then(response => {
this.listLoading = false;
this.list = response.data.list;
this.total = response.data.total;
});
},
getBrandList() {
fetchBrandList({pageNum: 1, pageSize: 100}).then(response => {
this.brandOptions = [];
let brandList = response.data.list;
for (let i = 0; i < brandList.length; i++) {
this.brandOptions.push({label: brandList[i].name, value: brandList[i].id});
}
});
},
getProductCateList() {
fetchListWithChildren().then(response => {
let list = response.data;
this.productCateOptions = [];
for (let i = 0; i < list.length; i++) {
let children = [];
if (list[i].children != null && list[i].children.length > 0) {
for (let j = 0; j < list[i].children.length; j++) {
children.push({label: list[i].children[j].name, value: list[i].children[j].id});
}
}
this.productCateOptions.push({label: list[i].name, value: list[i].id, children: children});
}
});
},
handleShowSkuEditDialog(index,row){
this.editSkuInfo.dialogVisible=true;
this.editSkuInfo.productId=row.id;
this.editSkuInfo.productSn=row.productSn;
this.editSkuInfo.productAttributeCategoryId = row.productAttributeCategoryId;
this.editSkuInfo.keyword=null;
fetchSkuStockList(row.id,{keyword:this.editSkuInfo.keyword}).then(response=>{
this.editSkuInfo.stockList=response.data;
});
if(row.productAttributeCategoryId!=null){
fetchProductAttrList(row.productAttributeCategoryId,{type:0}).then(response=>{
this.editSkuInfo.productAttr=response.data.list;
});
}
},
handleSearchEditSku(){
fetchSkuStockList(this.editSkuInfo.productId,{keyword:this.editSkuInfo.keyword}).then(response=>{
this.editSkuInfo.stockList=response.data;
});
},
handleEditSkuConfirm(){
if(this.editSkuInfo.stockList==null||this.editSkuInfo.stockList.length<=0){
this.$message({
message: '暂无sku信息',
type: 'warning',
duration: 1000
});
return
}
this.$confirm('是否要进行修改', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(()=>{
updateSkuStockList(this.editSkuInfo.productId,this.editSkuInfo.stockList).then(response=>{
this.$message({
message: '修改成功',
type: 'success',
duration: 1000
});
this.editSkuInfo.dialogVisible=false;
});
});
},
handleSearchList() {
this.listQuery.pageNum = 1;
this.getList();
},
handleAddProduct() {
this.$router.push({path:'/pms/addProduct'});
},
handleBatchOperate() {
if(this.operateType==null){
this.$message({
message: '请选择操作类型',
type: 'warning',
duration: 1000
});
return;
}
if(this.multipleSelection==null||this.multipleSelection.length<1){
this.$message({
message: '请选择要操作的商品',
type: 'warning',
duration: 1000
});
return;
}
this.$confirm('是否要进行该批量操作?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
let ids=[];
for(let i=0;i<this.multipleSelection.length;i++){
ids.push(this.multipleSelection[i].id);
}
switch (this.operateType) {
case this.operates[0].value:
this.updatePublishStatus(1,ids);
break;
case this.operates[1].value:
this.updatePublishStatus(0,ids);
break;
case this.operates[2].value:
this.updateRecommendStatus(1,ids);
break;
case this.operates[3].value:
this.updateRecommendStatus(0,ids);
break;
case this.operates[4].value:
this.updateNewStatus(1,ids);
break;
case this.operates[5].value:
this.updateNewStatus(0,ids);
break;
case this.operates[6].value:
break;
case this.operates[7].value:
this.updateDeleteStatus(1,ids);
break;
default:
break;
}
this.getList();
});
},
handleSizeChange(val) {
this.listQuery.pageNum = 1;
this.listQuery.pageSize = val;
this.getList();
},
handleCurrentChange(val) {
this.listQuery.pageNum = val;
this.getList();
},
handleSelectionChange(val) {
this.multipleSelection = val;
},
handlePublishStatusChange(index, row) {
let ids = [];
ids.push(row.id);
this.updatePublishStatus(row.publishStatus, ids);
},
handleNewStatusChange(index, row) {
let ids = [];
ids.push(row.id);
this.updateNewStatus(row.newStatus, ids);
},
handleRecommendStatusChange(index, row) {
let ids = [];
ids.push(row.id);
this.updateRecommendStatus(row.recommandStatus, ids);
},
handleResetSearch() {
this.selectProductCateValue = [];
this.listQuery = Object.assign({}, defaultListQuery);
},
handleDelete(index, row){
this.$confirm('是否要进行删除操作?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
let ids = [];
ids.push(row.id);
this.updateDeleteStatus(1,ids);
});
},
handleUpdateProduct(index,row){
this.$router.push({path:'/pms/updateProduct',query:{id:row.id}});
},
handleShowProduct(index,row){
console.log("handleShowProduct",row);
},
handleShowVerifyDetail(index,row){
console.log("handleShowVerifyDetail",row);
},
handleShowLog(index,row){
console.log("handleShowLog",row);
},
updatePublishStatus(publishStatus, ids) {
let params = new URLSearchParams();
params.append('ids', ids);
params.append('publishStatus', publishStatus);
updatePublishStatus(params).then(response => {
this.$message({
message: '修改成功',
type: 'success',
duration: 1000
});
});
},
updateNewStatus(newStatus, ids) {
let params = new URLSearchParams();
params.append('ids', ids);
params.append('newStatus', newStatus);
updateNewStatus(params).then(response => {
this.$message({
message: '修改成功',
type: 'success',
duration: 1000
});
});
},
updateRecommendStatus(recommendStatus, ids) {
let params = new URLSearchParams();
params.append('ids', ids);
params.append('recommendStatus', recommendStatus);
updateRecommendStatus(params).then(response => {
this.$message({
message: '修改成功',
type: 'success',
duration: 1000
});
});
},
updateDeleteStatus(deleteStatus, ids) {
let params = new URLSearchParams();
params.append('ids', ids);
params.append('deleteStatus', deleteStatus);
updateDeleteStatus(params).then(response => {
this.$message({
message: '删除成功',
type: 'success',
duration: 1000
});
});
this.getList();
}
}
}
</script>
<style></style>
#abc
<template>
<!-- 最外层的 div 元素设置了类名为 `app-container`用于包裹整个页面内容作为整体布局的容器 -->
<div class="app-container">
<!-- 使用 Element UI `el-card` 组件创建一个卡片式容器类名为 `filter-container`设置阴影效果为无用于放置筛选搜索相关的内容 -->
<el-card class="filter-container" shadow="never">
<div>
<!-- 使用一个图标元素这里是 Element UI 的搜索图标用于在视觉上提示筛选搜索功能 -->
<i class="el-icon-search"></i>
<!-- 显示筛选搜索文字用于明确功能区域的用途 -->
<span>筛选搜索</span>
<!-- 使用 Element UI `el-button` 按钮组件设置样式为右浮动点击时触发 `handleSearchList` 方法按钮类型为主要按钮通常有突出显示效果尺寸为小用于执行查询结果的操作 -->
<el-button
style="float: right"
@click="handleSearchList()"
type="primary"
size="small">
查询结果
</el-button>
<!-- 使用 Element UI `el-button` 按钮组件设置样式为右浮动且与右侧有 15px 的间隔点击时触发 `handleResetSearch` 方法尺寸为小用于执行重置筛选条件的操作 -->
<el-button
style="float: right;margin-right: 15px"
@click="handleResetSearch()"
size="small">
重置
</el-button>
</div>
<div style="margin-top: 15px">
<!-- 使用 Element UI `el-form` 表单组件设置表单为内联模式表单元素在一行显示绑定数据模型为 `listQuery`尺寸为小标签宽度为 140px用于放置各种筛选条件输入框 -->
<el-form :inline="true" :model="listQuery" size="small" label-width="140px">
<!-- 表单项标签显示为输入搜索内部使用 `el-input` 输入框组件设置宽度为 203px双向绑定 `listQuery.keyword` 数据有占位提示文字商品名称用于输入搜索关键词按商品名称进行筛选 -->
<el-form-item label="输入搜索:">
<el-input style="width: 203px" v-model="listQuery.keyword" placeholder="商品名称"></el-input>
</el-form-item>
<!-- 表单项标签显示为商品货号内部使用 `el-input` 输入框组件设置宽度为 203px双向绑定 `listQuery.productSn` 数据有占位提示文字商品货号用于输入商品货号进行筛选 -->
<el-form-item label="商品货号:">
<el-input style="width: 203px" v-model="listQuery.productSn" placeholder="商品货号"></el-input>
</el-form-item>
<!-- 表单项标签显示为商品分类内部使用 `el-cascader` 级联选择器组件设置可清除选择内容双向绑定 `selectProductCateValue` 数据其选项数据由 `productCateOptions` 提供用于选择商品分类进行筛选 -->
<el-form-item label="商品分类:">
<el-cascader
clearable
v-model="selectProductCateValue"
:options="productCateOptions">
</el-cascader>
</el-form-item>
<!-- 表单项标签显示为商品品牌内部使用 `el-select` 下拉选择框组件双向绑定 `listQuery.brandId` 数据有占位提示文字请选择品牌设置可清除选择内容其下拉选项通过循环 `brandOptions` 数组生成用于选择商品品牌进行筛选 -->
<el-form-item label="商品品牌:">
<el-select v-model="listQuery.brandId" placeholder="请选择品牌" clearable>
<el-option
v-for="item in brandOptions"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<!-- 表单项标签显示为上架状态内部使用 `el-select` 下拉选择框组件双向绑定 `listQuery.publishStatus` 数据有占位提示文字全部设置可清除选择内容其下拉选项由 `publishStatusOptions` 数组提供用于选择商品上架状态进行筛选 -->
<el-form-item label="上架状态:">
<el-select v-model="listQuery.publishStatus" placeholder="全部" clearable>
<el-option
v-for="item in publishStatusOptions"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<!-- 表单项标签显示为审核状态内部使用 `el-select` 下拉选择框组件双向绑定 `listQuery.verifyStatus` 数据有占位提示文字全部设置可清除选择内容其下拉选项由 `verifyStatusOptions` 数组提供用于选择商品审核状态进行筛选 -->
<el-form-item label="审核状态:">
<el-select v-model="listQuery.verifyStatus" placeholder="全部" clearable>
<el-option
v-for="item in verifyStatusOptions"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
</el-form>
</div>
</el-card>
<!-- 使用 Element UI `el-card` 组件创建一个卡片式容器类名为 `operate-container`设置阴影效果为无用于放置数据列表操作相关的内容 -->
<el-card class="operate-container" shadow="never">
<!-- 使用一个图标元素这里是 Element UI 的票务图标可能用于视觉上提示与数据操作相关的功能 -->
<i class="el-icon-tickets"></i>
<!-- 显示数据列表文字用于明确功能区域的用途 -->
<span>数据列表</span>
<!-- 使用 Element UI `el-button` 按钮组件设置类名为 `btn-add`点击时触发 `handleAddProduct` 方法尺寸为迷你型用于执行添加商品的操作 -->
<el-button
class="btn-add"
@click="handleAddProduct()"
size="mini">
添加
</el-button>
</el-card>
<!-- 一个 div 元素设置类名为 `table-container`用于包裹数据列表表格作为表格展示的容器 -->
<div class="table-container">
<!-- 使用 Element UI `el-table` 表格组件设置了表格引用名为 `productTable`绑定数据为 `list`宽度为 100%监听行选择变化事件 `selection-change` 并触发 `handleSelectionChange` 方法根据 `listLoading` 变量控制加载状态显示设置表格边框用于展示商品数据列表 -->
<el-table ref="productTable"
:data="list"
style="width: 100%"
@selection-change="handleSelectionChange"
v-loading="listLoading"
border>
<!-- 表格列类型为选择列可勾选行宽度为 60px内容居中对齐用于选择表格中的行数据 -->
<el-table-column type="selection" width="60" align="center"></el-table-column>
<!-- 表格列标签显示为编号宽度为 100px内容居中对齐通过插槽作用域获取当前行数据展示商品编号信息 -->
<el-table-column label="编号" width="100" align="center">
<template slot-scope="scope">{{scope.row.id}}</template>
</el-table-column>
<!-- 表格列标签显示为商品图片宽度为 120px内容居中对齐通过插槽作用域获取当前行数据展示商品图片信息图片设置了高度为 80px -->
<el-table-column label="商品图片" width="120" align="center">
<template slot-scope="scope"><img style="height: 80px" :src="scope.row.pic"></template>
</el-table-column>
<!-- 表格列标签显示为商品名称内容居中对齐通过插槽作用域获取当前行数据展示商品名称以及品牌信息 -->
<el-table-column label="商品名称" align="center">
<template slot-scope="scope">
<p>{{scope.row.name}}</p>
<p>品牌{{scope.row.brandName}}</p>
</template>
</el-table-column>
<!-- 表格列标签显示为价格/货号宽度为 120px内容居中对齐通过插槽作用域获取当前行数据展示商品价格和货号信息 -->
<el-table-column label="价格/货号" width="120" align="center">
<template slot-scope="scope">
<p>价格{{scope.row.price}}</p>
<p>货号{{scope.row.productSn}}</p>
</template>
</el-table-column>
<!-- 表格列标签显示为标签宽度为 140px内容居中对齐通过插槽作用域获取当前行数据包含多个开关组件用于展示和操作商品的上架新品推荐等状态信息 -->
<el-table-column label="标签" width="140" align="center">
<template slot-scope="scope">
<p>上架
<!-- 使用 `el-switch` 开关组件监听开关状态变化事件 `change` 并触发对应的方法设置激活值和非激活值双向绑定当前行的 `publishStatus` 数据用于控制商品上架状态 -->
<el-switch
@change="handlePublishStatusChange(scope.$index, scope.row)"
:active-value="1"
:inactive-value="0"
v-model="scope.row.publishStatus">
</el-switch>
</p>
<p>新品
<!-- 使用 `el-switch` 开关组件监听开关状态变化事件 `change` 并触发对应的方法设置激活值和非激活值双向绑定当前行的 `newStatus` 数据用于控制商品是否为新品状态 -->
<el-switch
@change="handleNewStatusChange(scope.$index, scope.row)"
:active-value="1"
:inactive-value="0"
v-model="scope.row.newStatus">
</el-switch>
</p>
<p>推荐
<!-- 使用 `el-switch` 开关组件监听开关状态变化事件 `change` 并触发对应的方法设置激活值和非激活值双向绑定当前行的 `recommandStatus` 数据用于控制商品是否为推荐状态 -->
<el-switch
@change="handleRecommendStatusChange(scope.$index, scope.row)"
:active-value="1"
:inactive-value="0"
v-model="scope.row.recommandStatus">
</el-switch>
</p>
</template>
</el-table-column>
<!-- 表格列标签显示为排序宽度为 100px内容居中对齐通过插槽作用域获取当前行数据展示商品排序信息 -->
<el-table-column label="排序" width="100" align="center">
<template slot-scope="scope">{{scope.row.sort}}</template>
</el-table-column>
<!-- 表格列标签显示为SKU 库存宽度为 100px内容居中对齐通过插槽作用域获取当前行数据包含一个编辑按钮点击触发 `handleShowSkuEditDialog` 方法用于查看和编辑商品 SKU 库存信息 -->
<el-table-column label="SKU 库存" width="100" align="center">
<template slot-scope="scope">
<el-button type="primary" icon="el-icon-edit" @click="handleShowSkuEditDialog(scope.$index, scope.row)" circle></el-button>
</template>
</el-table-column>
<!-- 表格列标签显示为销量宽度为 100px内容居中对齐通过插槽作用域获取当前行数据展示商品销量信息 -->
<el-table-column label="销量" width="100" align="center">
<template slot-scope="scope">{{scope.row.sale}}</template>
</el-table-column>
<!-- 表格列标签显示为审核状态宽度为 100px内容居中对齐通过插槽作用域获取当前行数据展示商品审核状态信息同时包含一个查看审核详情的按钮点击触发 `handleShowVerifyDetail` 方法 -->
<el-table-column label="审核状态" width="100" align="center">
<template slot-scope="scope">
<p>{{scope.row.verifyStatus | verifyStatusFilter}}</p>
<p>
<el-button
type="text"
@click="handleShowVerifyDetail(scope.$index, scope.row)">审核详情
</el-button>
</p>
</template>
</el-table-column>
<!-- 表格列标签显示为操作宽度为 160px内容居中对齐通过插槽作用域获取当前行数据包含多个操作按钮如查看编辑查看日志删除等分别触发对应的方法用于对商品进行各种操作 -->
<el-table-column label="操作" width="160" align="center">
<template slot-scope="scope">
<p>
<el-button
size="mini"
@click="handleShowProduct(scope.$index, scope.row)">查看
</el-button>
<el-button
size="mini"
@click="handleUpdateProduct(scope.$index, scope.row)">编辑
</el-button>
</p>
<p>
<el-button
size="mini"
@click="handleShowLog(scope.$index, scope.row)">日志
</el-button>
<el-button
size="mini"
type="danger"
@click="handleDelete(scope.$index, scope.row)">删除
</el-button>
</p>
</template>
</el-table-column>
</el-table>
</div>
<!-- 一个 div 元素设置类名为 `batch-operate-container`用于放置批量操作相关的内容如选择批量操作类型和执行操作的按钮 -->
<div class="batch-operate-container">
<!-- 使用 Element UI `el-select` 下拉选择框组件尺寸为小双向绑定 `operateType` 数据有占位提示文字批量操作其下拉选项通过循环 `operates` 数组生成用于选择批量操作的类型 -->
<el-select
size="small"
v-model="operateType" placeholder="批量操作">
<el-option
v-for="item in operates"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
<!-- 使用 Element UI `el-button` 按钮组件设置样式为左距 20px类名为 `search-button`点击时触发 `handleBatchOperate` 方法按钮类型为主要按钮通常有突出显示效果尺寸为小用于执行确定批量操作的动作 -->
<el-button
style="margin-left: 20px"
class="search-button"
@click="handleBatchOperate()"
type="primary"
size="small">
确定
</el-button>
</div>
<!-- 一个 div 元素设置类名为 `pagination-container`用于包裹分页组件作为分页功能的容器 -->
<div class="pagination-container">
<!-- 使用 Element UI `el-pagination` 分页组件设置背景色样式监听页面尺寸变化和当前页变化事件并分别触发对应的方法设置分页布局样式绑定页面尺寸可选页面尺寸数组当前页码以及总数据量等数据用于实现数据列表的分页功能 -->
<el-pagination
background
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
layout="total, sizes,prev, pager, next,jumper"
:page-size="listQuery.pageSize"
:page-sizes="[5,10,15]"
:current-page.sync="listQuery.pageNum"
:total="total">
</el-pagination>
</div>
<!-- 使用 Element UI `el-dialog` 对话框组件设置标题为编辑货品信息通过双向绑定控制对话框的显示隐藏状态宽度设置为 40%用于展示和编辑商品 SKU 库存相关的详细信息 -->
<el-dialog
title="编辑货品信息"
:visible.sync="editSkuInfo.dialogVisible"
width="40%">
<span>商品货号

@ -0,0 +1,30 @@
<template> 
<product-detail :is-edit='true'></product-detail>
</template>
<script>
import ProductDetail from './components/ProductDetail'
export default {
name: 'updateProduct',
components: { ProductDetail }
}
</script>
<style>
</style>
#abc
<template>
<!-- 此处使用了名为 `product-detail` 的自定义组件通过 `:is-edit='true'` 属性绑定 `product-detail` 组件传递了一个名为 `is-edit` 的属性值为 `true`意味着当前是编辑商品的操作模式与新增商品模式相区别`product-detail` 组件应该会根据这个属性来展示相应的编辑界面以及执行对应的逻辑 -->
<product-detail :is-edit='true'></product-detail>
</template>
<script>
// `./components/` `ProductDetail`
import ProductDetail from './components/ProductDetail';
export default {
name: 'updateProduct',
// `components` `ProductDetail` 使`updateProduct`使 `<product-detail>` Vue.js 便使
components: { ProductDetail }
}
</script>
<style>
/* 这里的样式部分目前为空,可根据 `updateProduct` 组件整体的视觉呈现需求,添加相应的 CSS 样式规则,例如对整个组件布局进行调整,设置组件内各元素的字体、颜色、间距等样式属性,使其在页面中展现出符合预期的外观效果 */
</style>

@ -0,0 +1,32 @@
<template>
<product-attr-detail :is-edit='false'></product-attr-detail>
</template>
<script>
import ProductAttrDetail from './components/ProductAttrDetail'
export default {
name: 'addProductAttr',
components: { ProductAttrDetail }
}
</script>
<style scoped>
</style>
#abc
<template>
<!-- 使用名为 `product-attr-detail` 的自定义组件通过 `:is-edit='false'` 属性绑定向该组件传递了名为 `is-edit` 的属性值设为 `false`表示当前处于添加商品属性的操作模式区别于编辑商品属性模式`product-attr-detail` 组件会依据这个属性值来展示对应的界面以及执行相应的添加相关逻辑 -->
<product-attr-detail :is-edit='false'></product-attr-detail>
</template>
<script>
// `./components/` `ProductAttrDetail`
import ProductAttrDetail from './components/ProductAttrDetail';
export default {
name: 'addProductAttr',
// `components` `ProductAttrDetail` 使 `addProductAttr` `<product-attr-detail>` Vue.js 便
components: { ProductAttrDetail }
}
</script>
<style scoped>
/* 这里样式部分设置了 `scoped` 属性,意味着样式只会应用到当前组件内部的元素上。目前该部分样式为空,后续可以根据 `addProductAttr` 组件的视觉设计要求,添加对应的 CSS 样式规则,像设置组件内文本的字体、颜色,表单元素的布局、边框样式等,让组件在页面中呈现出期望的外观效果 */
</style>

@ -0,0 +1,185 @@
<template>
<el-card class="form-container" shadow="never">
<el-form :model="productAttr" :rules="rules" ref="productAttrFrom" label-width="150px">
<el-form-item label="属性名称:" prop="name">
<el-input v-model="productAttr.name"></el-input>
</el-form-item>
<el-form-item label="商品类型:">
<el-select v-model="productAttr.productAttributeCategoryId" placeholder="请选择">
<el-option
v-for="item in productAttrCateList"
:key="item.id"
:label="item.name"
:value="item.id">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="分类筛选样式:">
<el-radio-group v-model="productAttr.filterType">
<el-radio :label="0">普通</el-radio>
<el-radio :label="1">颜色</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="能否进行检索:">
<el-radio-group v-model="productAttr.searchType">
<el-radio :label="0">不需要检索</el-radio>
<el-radio :label="1">关键字检索</el-radio>
<el-radio :label="2">范围检索</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="商品属性关联:">
<el-radio-group v-model="productAttr.relatedStatus">
<el-radio :label="1"></el-radio>
<el-radio :label="0"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="属性是否可选:">
<el-radio-group v-model="productAttr.selectType">
<el-radio :label="0">唯一</el-radio>
<el-radio :label="1">单选</el-radio>
<el-radio :label="2">复选</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="属性值的录入方式:">
<el-radio-group v-model="productAttr.inputType">
<el-radio :label="0">手工录入</el-radio>
<el-radio :label="1">从下面列表中选择</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="属性值可选值列表:">
<el-input :autosize="true" type="textarea" v-model="inputListFormat"></el-input>
</el-form-item>
<el-form-item label="是否支持手动新增:">
<el-radio-group v-model="productAttr.handAddStatus">
<el-radio :label="1"></el-radio>
<el-radio :label="0"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="排序属性:">
<el-input v-model="productAttr.sort"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit('productAttrFrom')"></el-button>
<el-button v-if="!isEdit" @click="resetForm('productAttrFrom')"></el-button>
</el-form-item>
</el-form>
</el-card>
</template>
<script>
import {fetchList} from '@/api/productAttrCate'
import {createProductAttr,getProductAttr,updateProductAttr} from '@/api/productAttr'
const defaultProductAttr = {
filterType: 0,
handAddStatus: 0,
inputList: '',
inputType: 0,
name: '',
productAttributeCategoryId: 0,
relatedStatus: 0,
searchType: 0,
selectType: 0,
sort: 0,
type: 0
};
export default {
name: "ProductAttrDetail",
props: {
isEdit: {
type: Boolean,
default: false
}
},
data() {
return {
productAttr: Object.assign({}, defaultProductAttr),
rules: {
name: [
{required: true, message: '请输入属性名称', trigger: 'blur'},
{min: 2, max: 140, message: '长度在 2 到 140 个字符', trigger: 'blur'}
]
},
productAttrCateList: null,
inputListFormat:null
}
},
created() {
if(this.isEdit){
getProductAttr(this.$route.query.id).then(response => {
this.productAttr = response.data;
this.inputListFormat = this.productAttr.inputList.replace(/,/g,'\n');
});
}else{
this.resetProductAttr();
}
this.getCateList();
},
watch:{
inputListFormat: function (newValue, oldValue) {
newValue = newValue.replace(/\n/g,',');
this.productAttr.inputList = newValue;
}
},
methods: {
getCateList() {
let listQuery = {pageNum: 1, pageSize: 100};
fetchList(listQuery).then(response => {
this.productAttrCateList = response.data.list;
});
},
resetProductAttr() {
this.productAttr = Object.assign({}, defaultProductAttr);
this.productAttr.productAttributeCategoryId = Number(this.$route.query.cid);
this.productAttr.type = Number(this.$route.query.type);
},
onSubmit(formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
this.$confirm('是否提交数据', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
if(this.isEdit){
updateProductAttr(this.$route.query.id,this.productAttr).then(response=>{
this.$message({
message: '修改成功',
type: 'success',
duration: 1000
});
this.$router.back();
});
}else{
createProductAttr(this.productAttr).then(response=>{
this.$message({
message: '提交成功',
type: 'success',
duration: 1000
});
this.resetForm('productAttrFrom');
});
}
});
} else {
this.$message({
message: '验证失败',
type: 'error',
duration: 1000
});
return false;
}
});
},
resetForm(formName) {
this.$refs[formName].resetFields();
this.resetProductAttr();
}
},
}
</script>
<style scoped>
</style>

@ -0,0 +1,463 @@
<template> 
<div class="app-container">
<el-card class="operate-container" shadow="never">
<i class="el-icon-tickets" style="margin-top: 5px"></i>
<span style="margin-top: 5px">数据列表</span>
<el-button
class="btn-add"
@click="addProductAttrCate()"
size="mini">
添加
</el-button>
</el-card>
<div class="table-container">
<el-table ref="productAttrCateTable"
style="width: 100%"
:data="list"
v-loading="listLoading"
border>
<el-table-column label="编号" width="100" align="center">
<template slot-scope="scope">{{scope.row.id}}</template>
</el-table-column>
<el-table-column label="类型名称" align="center">
<template slot-scope="scope">{{scope.row.name}}</template>
</el-table-column>
<el-table-column label="属性数量" width="200" align="center">
<template slot-scope="scope">{{scope.row.attributeCount==null?0:scope.row.attributeCount}}</template>
</el-table-column>
<el-table-column label="参数数量" width="200" align="center">
<template slot-scope="scope">{{scope.row.paramCount==null?0:scope.row.paramCount}}</template>
</el-table-column>
<el-table-column label="设置" width="200" align="center">
<template slot-scope="scope">
<el-button
size="mini"
@click="getAttrList(scope.$index, scope.row)">属性列表
</el-button>
<el-button
size="mini"
@click="getParamList(scope.$index, scope.row)">参数列表
</el-button>
</template>
</el-table-column>
<el-table-column label="操作" width="200" align="center">
<template slot-scope="scope">
<el-button
size="mini"
@click="handleUpdate(scope.$index, scope.row)">编辑
</el-button>
<el-button
size="mini"
type="danger"
@click="handleDelete(scope.$index, scope.row)">删除
</el-button>
</template>
</el-table-column>
</el-table>
</div>
<div class="pagination-container">
<el-pagination
background
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
layout="total, sizes,prev, pager, next,jumper"
:page-size="listQuery.pageSize"
:page-sizes="[5,10,15]"
:current-page.sync="listQuery.pageNum"
:total="total">
</el-pagination>
</div>
<el-dialog
:title="dialogTitle"
:visible.sync="dialogVisible"
:before-close="handleClose()"
width="30%">
<el-form ref="productAttrCatForm":model="productAttrCate" :rules="rules" label-width="120px">
<el-form-item label="类型名称" prop="name">
<el-input v-model="productAttrCate.name" auto-complete="off"></el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false"> </el-button>
<el-button type="primary" @click="handleConfirm('productAttrCatForm')"> </el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import {fetchList,createProductAttrCate,deleteProductAttrCate,updateProductAttrCate} from '@/api/productAttrCate'
export default {
name: 'productAttrCateList',
data() {
return {
list: null,
total: null,
listLoading: true,
listQuery: {
pageNum: 1,
pageSize: 5
},
dialogVisible: false,
dialogTitle:'',
productAttrCate:{
name:'',
id:null
},
rules: {
name: [
{ required: true, message: '请输入类型名称', trigger: 'blur' }
]
}
}
},
created() {
this.getList();
},
methods: {
getList() {
this.listLoading = true;
fetchList(this.listQuery).then(response => {
this.listLoading = false;
this.list = response.data.list;
this.total = response.data.total;
});
},
addProductAttrCate() {
this.dialogVisible = true;
this.dialogTitle = "添加类型";
},
handleSizeChange(val) {
this.listQuery.pageNum = 1;
this.listQuery.pageSize = val;
this.getList();
},
handleCurrentChange(val) {
this.listQuery.pageNum = val;
this.getList();
},
handleDelete(index, row) {
this.$confirm('是否要删除该品牌', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
deleteProductAttrCate(row.id).then(response=>{
this.$message({
message: '删除成功',
type: 'success',
duration:1000
});
this.getList();
});
});
},
handleUpdate(index, row) {
this.dialogVisible = true;
this.dialogTitle = "编辑类型";
this.productAttrCate.name = row.name;
this.productAttrCate.id = row.id;
},
getAttrList(index, row) {
this.$router.push({path: '/pms/productAttrList',query:{cid:row.id,cname:row.name,type:0}})
},
getParamList(index, row) {
this.$router.push({path: '/pms/productAttrList',query:{cid:row.id,cname:row.name,type:1}})
},
handleConfirm(formName){
this.$refs[formName].validate((valid) => {
if (valid) {
let data = new URLSearchParams();
data.append("name",this.productAttrCate.name);
if(this.dialogTitle==="添加类型"){
createProductAttrCate(data).then(response=>{
this.$message({
message: '添加成功',
type: 'success',
duration:1000
});
this.dialogVisible = false;
this.getList();
});
}else{
updateProductAttrCate(this.productAttrCate.id,data).then(response=>{
this.$message({
message: '修改成功',
type: 'success',
duration:1000
});
this.dialogVisible = false;
this.getList();
});
}
} else {
console.log('error submit!!');
return false;
}
});
},
handleClose(){
if (!this.dialogVisible && this.$refs.productAttrCatForm) {
this.$refs.productAttrCatForm.clearValidate()
}
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
</style>
#abc
<template>
<!-- 最外层的 div 元素设置了类名为 `app-container`作为整个页面内容的容器用于整体布局和包裹内部的各个功能模块 -->
<div class="app-container">
<!-- 使用 Element UI `el-card` 组件创建一个卡片式容器类名为 `operate-container`设置阴影效果为无用于放置与数据列表操作相关的内容比如添加按钮等 -->
<el-card class="operate-container" shadow="never">
<!-- 使用 Element UI 的图标组件展示票务图标并设置了距顶部 5px margin用于在视觉上提示与数据操作相关的功能起到一定的表意作用 -->
<i class="el-icon-tickets" style="margin-top: 5px"></i>
<!-- 显示数据列表文字同样设置了距顶部 5px margin用于明确此卡片容器内操作主要针对的数据列表功能区域 -->
<span style="margin-top: 5px">数据列表</span>
<!-- 使用 Element UI `el-button` 按钮组件设置类名为 `btn-add`点击时触发 `addProductAttrCate` 方法尺寸为迷你型用于执行添加商品属性分类的操作 -->
<el-button
class="btn-add"
@click="addProductAttrCate()"
size="mini">
添加
</el-button>
</el-card>
<!-- 一个 div 元素设置类名为 `table-container`作为表格展示的容器用于包裹下面的 `el-table` 组件呈现数据列表内容 -->
<div class="table-container">
<!-- 使用 Element UI `el-table` 表格组件设置了表格引用名为 `productAttrCateTable`宽度为 100%绑定数据为 `list`根据 `listLoading` 变量控制加载状态显示设置表格边框用于展示商品属性分类相关的数据列表 -->
<el-table ref="productAttrCateTable"
style="width: 100%"
:data="list"
v-loading="listLoading"
border>
<!-- 表格列标签显示为编号宽度为 100px内容居中对齐通过插槽作用域获取当前行数据展示商品属性分类的编号信息 -->
<el-table-column label="编号" width="100" align="center">
<template slot-scope="scope">{{scope.row.id}}</template>
</el-table-column>
<!-- 表格列标签显示为类型名称内容居中对齐通过插槽作用域获取当前行数据展示商品属性分类的类型名称信息 -->
<el-table-column label="类型名称" align="center">
<template slot-scope="scope">{{scope.row.name}}</template>
</el-table-column>
<!-- 表格列标签显示为属性数量宽度为 200px内容居中对齐通过插槽作用域获取当前行数据展示商品属性分类对应的属性数量信息如果数量为 `null`则显示为 0 -->
<el-table-column label="属性数量" width="200" align="center">
<template slot-scope="scope">{{scope.row.attributeCount==null?0:scope.row.attributeCount}}</template>
</el-table-column>
<!-- 表格列标签显示为参数数量宽度为 200px内容居中对齐通过插槽作用域获取当前行数据展示商品属性分类对应的参数数量信息如果数量为 `null`则显示为 0 -->
<el-table-column label="参数数量" width="200" align="center">
<template slot-scope="scope">{{scope.row.paramCount==null?0:scope.row.paramCount}}</template>
</el-table-column>
<!-- 表格列标签显示为设置宽度为 200px内容居中对齐通过插槽作用域获取当前行数据包含两个按钮分别点击可跳转到对应的属性列表和参数列表页面用于查看相应详细信息 -->
<el-table-column label="设置" width="200" align="center">
<template slot-scope="scope">
<el-button
size="mini"
@click="getAttrList(scope.$index, scope.row)">属性列表
</el-button>
<el-button
size="mini"
@click="getParamList(scope.$index, scope.row)">参数列表
</el-button>
</template>
</el-table-column>
<!-- 表格列标签显示为操作宽度为 200px内容居中对齐通过插槽作用域获取当前行数据包含编辑和删除两个按钮分别点击可触发对应的编辑和删除操作方法 -->
<el-table-column label="操作" width="200" align="center">
<template slot-scope="scope">
<el-button
size="mini"
@click="handleUpdate(scope.$index, scope.row)">编辑
</el-button>
<el-button
size="mini"
type="danger"
@click="handleDelete(scope.$index, scope.row)">删除
</el-button>
</template>
</el-table-column>
</el-table>
</div>
<!-- 一个 div 元素设置类名为 `pagination-container`用于包裹分页组件作为分页功能的容器 -->
<div class="pagination-container">
<!-- 使用 Element UI `el-pagination` 分页组件设置背景色样式监听页面尺寸变化和当前页变化事件并分别触发对应的方法设置分页布局样式绑定页面尺寸可选页面尺寸数组当前页码以及总数据量等数据用于实现数据列表的分页功能 -->
<el-pagination
background
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
layout="total, sizes,prev, pager, next,jumper"
:page-size="listQuery.pageSize"
:page-sizes="[5,10,15]"
:current-page.sync="listQuery.pageNum"
:total="total">
</el-pagination>
</div>
<!-- 使用 Element UI `el-dialog` 对话框组件通过双向绑定控制对话框的标题显示隐藏状态以及关闭前的操作宽度设置为 30%用于弹出添加或编辑商品属性分类的表单界面 -->
<el-dialog
:title="dialogTitle"
:visible.sync="dialogVisible"
:before-close="handleClose()"
width="30%">
<!-- 使用 Element UI `el-form` 表单组件设置了表单引用名绑定数据模型为 `productAttrCate`关联表单验证规则 `rules`标签宽度为 120px用于放置添加或编辑商品属性分类的输入框等表单元素 -->
<el-form ref="productAttrCatForm":model="productAttrCate" :rules="rules" label-width="120px">
<!-- 表单项标签显示为类型名称指定了验证属性 `prop` `name`内部使用 `el-input` 输入框组件双向绑定 `productAttrCate.name` 数据设置自动补全功能为关闭用于输入商品属性分类的名称 -->
<el-form-item label="类型名称" prop="name">
<el-input v-model="productAttrCate.name" auto-complete="off"></el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<!-- 使用 Element UI `el-button` 按钮组件点击时隐藏对话框用于取消添加或编辑操作 -->
<el-button @click="dialogVisible = false"> </el-button>
<!-- 使用 Element UI `el-button` 按钮组件点击时触发 `handleConfirm` 方法并传入表单引用名按钮类型为主要按钮通常有突出显示效果用于确认添加或编辑操作 -->
<el-button type="primary" @click="handleConfirm('productAttrCatForm')"> </el-button>
</span>
</el-dialog>
</div>
</template>
<script>
// `@/api/productAttrCate` API
import {fetchList,createProductAttrCate,deleteProductAttrCate,updateProductAttrCate} from '@/api/productAttrCate';
export default {
name: 'productAttrCateList',
data() {
return {
// `null`
list: null,
// `null`
total: null,
// `true` `false`
listLoading: true,
//
listQuery: {
pageNum: 1,
pageSize: 5
},
// `false`
dialogVisible: false,
//
dialogTitle: '',
// IDID `null`
productAttrCate: {
name: '',
id: null
},
// `name` `blur`
rules: {
name: [
{ required: true, message: '请输入类型名称', trigger: 'blur' }
]
}
};
},
created() {
// `getList`
this.getList();
},
methods: {
getList() {
// `listLoading` `true`
this.listLoading = true;
// `fetchList` `listQuery` `listLoading` `false` `list` `total`
fetchList(this.listQuery).then(response => {
this.listLoading = false;
this.list = response.data.list;
this.total = response.data.total;
});
},
addProductAttrCate() {
// `dialogVisible` `true`使 `dialogTitle`
this.dialogVisible = true;
this.dialogTitle = "添加类型";
},
handleSizeChange(val) {
// `listQuery` `pageNum` 1 `pageSize` `val` `getList`
this.listQuery.pageNum = 1;
this.listQuery.pageSize = val;
this.getList();
},
handleCurrentChange(val) {
// `listQuery` `pageNum` `val` `getList`
this.listQuery.pageNum = val;
this.getList();
},
handleDelete(index, row) {
// `deleteProductAttrCate` `id` `getList`
this.$confirm('是否要删除该品牌', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
deleteProductAttrCate(row.id).then(response => {
this.$message({
message: '删除成功',
type: 'success',
duration: 1000
});
this.getList();
});
});
},
handleUpdate(index, row) {
// `dialogVisible` `true`使 `dialogTitle` ID `productAttrCate`
this.dialogVisible = true;
this.dialogTitle = "编辑类型";
this.productAttrCate.name = row.name;
this.productAttrCate.id = row.id;
},
getAttrList(index, row) {
// `$router.push` `/pms/productAttrList` `cid` ID`cname` `type` 0
this.$router.push({path: '/pms/productAttrList',query:{cid:row.id,cname:row.name,type:0}})
},
getParamList(index, row) {
// `$router.push` `/pms/productAttrList` `cid` ID`cname` `type` 1
this.$router.push({path: '/pms/productAttrList',query:{cid:row.id,cname:row.name,type:1}})
},
handleConfirm(formName) {
// `$refs[formName].validate` `valid` `true``dialogTitle` `createProductAttrCate` `updateProductAttrCate` ID `valid` `false` `false`
this.$refs[formName].validate((valid) => {
if (valid) {
let data = new URLSearchParams();
data.append("name",this.productAttrCate.name);
if(this.dialogTitle==="添加类型"){
createProductAttrCate(data).then(response => {
this.$message({
message: '添加成功',
type: 'success',
duration: 1000
});
this.dialogVisible = false;
this.getList();
});
}else{
updateProductAttrCate(this.productAttrCate.id,data).then(response => {
this.$message({
message: '修改成功',
type: 'success',
duration: 1000
});
this.dialogVisible = false;
this.getList();
});
}
} else {
console.log('error submit!!');
return false;
}
});
},
handleClose() {
// `$refs.productAttrCatForm` `clearValidate`
if (!this.dialogVisible && this.$refs.productAttrCatForm) {
this.$refs.productAttrCatForm.clearValidate()
}
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>

@ -0,0 +1,455 @@
<template> 
<div class="app-container">
<el-card class="operate-container" shadow="never">
<i class="el-icon-tickets" style="margin-top: 5px"></i>
<span style="margin-top: 5px">数据列表</span>
<el-button
class="btn-add"
@click="addProductAttr()"
size="mini">
添加
</el-button>
</el-card>
<div class="table-container">
<el-table ref="productAttrTable"
:data="list"
style="width: 100%"
@selection-change="handleSelectionChange"
v-loading="listLoading"
border>
<el-table-column type="selection" width="60" align="center"></el-table-column>
<el-table-column label="编号" width="100" align="center">
<template slot-scope="scope">{{scope.row.id}}</template>
</el-table-column>
<el-table-column label="属性名称" width="140" align="center">
<template slot-scope="scope">{{scope.row.name}}</template>
</el-table-column>
<el-table-column label="商品类型" width="140" align="center">
<template slot-scope="scope">{{$route.query.cname}}</template>
</el-table-column>
<el-table-column label="属性是否可选" width="120" align="center">
<template slot-scope="scope">{{scope.row.selectType|selectTypeFilter}}</template>
</el-table-column>
<el-table-column label="属性值的录入方式" width="150" align="center">
<template slot-scope="scope">{{scope.row.inputType|inputTypeFilter}}</template>
</el-table-column>
<el-table-column label="可选值列表" align="center">
<template slot-scope="scope">{{scope.row.inputList}}</template>
</el-table-column>
<el-table-column label="排序" width="100" align="center">
<template slot-scope="scope">{{scope.row.sort}}</template>
</el-table-column>
<el-table-column label="操作" width="200" align="center">
<template slot-scope="scope">
<el-button
size="mini"
@click="handleUpdate(scope.$index, scope.row)">编辑
</el-button>
<el-button
size="mini"
type="danger"
@click="handleDelete(scope.$index, scope.row)">删除
</el-button>
</template>
</el-table-column>
</el-table>
</div>
<div class="batch-operate-container">
<el-select
size="small"
v-model="operateType" placeholder="批量操作">
<el-option
v-for="item in operates"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
<el-button
style="margin-left: 20px"
class="search-button"
@click="handleBatchOperate()"
type="primary"
size="small">
确定
</el-button>
</div>
<div class="pagination-container">
<el-pagination
background
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
layout="total, sizes,prev, pager, next,jumper"
:page-size="listQuery.pageSize"
:page-sizes="[5,10,15]"
:current-page.sync="listQuery.pageNum"
:total="total">
</el-pagination>
</div>
</div>
</template>
<script>
import {fetchList, deleteProductAttr} from '@/api/productAttr'
export default {
name: 'productAttrList',
data() {
return {
list: null,
total: null,
listLoading: true,
listQuery: {
pageNum: 1,
pageSize: 5,
type: this.$route.query.type
},
operateType: null,
multipleSelection: [],
operates: [
{
label: "删除",
value: "deleteProductAttr"
}
]
}
},
created() {
this.getList();
},
methods: {
getList() {
this.listLoading = true;
fetchList(this.$route.query.cid, this.listQuery).then(response => {
this.listLoading = false;
this.list = response.data.list;
this.total = response.data.total;
});
},
addProductAttr() {
this.$router.push({path:'/pms/addProductAttr',query:{cid:this.$route.query.cid,type:this.$route.query.type}});
},
handleSelectionChange(val) {
this.multipleSelection = val;
},
handleBatchOperate() {
if (this.multipleSelection < 1) {
this.$message({
message: '请选择一条记录',
type: 'warning',
duration: 1000
});
return;
}
if (this.operateType !== 'deleteProductAttr') {
this.$message({
message: '请选择批量操作类型',
type: 'warning',
duration: 1000
});
return;
}
let ids = [];
for (let i = 0; i < this.multipleSelection.length; i++) {
ids.push(this.multipleSelection[i].id);
}
this.handleDeleteProductAttr(ids);
},
handleSizeChange(val) {
this.listQuery.pageNum = 1;
this.listQuery.pageSize = val;
this.getList();
},
handleCurrentChange(val) {
this.listQuery.pageNum = val;
this.getList();
},
handleUpdate(index, row) {
this.$router.push({path:'/pms/updateProductAttr',query:{id:row.id}});
},
handleDeleteProductAttr(ids) {
this.$confirm('是否要删除该属性', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
let data = new URLSearchParams();
data.append("ids", ids);
deleteProductAttr(data).then(response => {
this.$message({
message: '删除成功',
type: 'success',
duration: 1000
});
this.getList();
});
});
},
handleDelete(index, row) {
let ids = [];
ids.push(row.id);
this.handleDeleteProductAttr(ids);
},
},
filters: {
inputTypeFilter(value) {
if (value === 1) {
return '从列表中选取';
} else {
return '手工录入'
}
},
selectTypeFilter(value) {
if (value === 1) {
return '单选';
} else if (value === 2) {
return '多选';
} else {
return '唯一'
}
},
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
</style>
#abc
<template>
<!-- 最外层的 div 元素类名为 `app-container`作为整个页面内容的容器用于包裹内部各个功能模块实现整体页面布局 -->
<div class="app-container">
<!-- 使用 Element UI `el-card` 组件创建一个卡片式容器类名为 `operate-container`设置阴影效果为 `never`无阴影用于放置与数据列表操作相关的元素比如操作按钮等 -->
<el-card class="operate-container" shadow="never">
<!-- 使用 Element UI 的图标组件展示票务图标并设置 `margin-top: 5px`使其在垂直方向上距顶部有一定间距在视觉上辅助提示该区域与数据操作相关 -->
<i class="el-icon-tickets" style="margin-top: 5px"></i>
<!-- 显示数据列表文字同样设置 `margin-top: 5px`明确该卡片容器内的操作是针对下方展示的数据列表起到功能说明作用 -->
<span style="margin-top: 5px">数据列表</span>
<!-- 使用 Element UI `el-button` 按钮组件设置类名为 `btn-add`点击时触发 `addProductAttr` 方法尺寸为 `mini`迷你型用于执行添加商品属性的操作 -->
<el-button
class="btn-add"
@click="addProductAttr()"
size="mini">
添加
</el-button>
</el-card>
<!-- div 元素类名为 `table-container`作为表格展示的容器用于包裹 `el-table` 组件使其在页面中以特定样式展示数据列表 -->
<div class="table-container">
<!-- 使用 Element UI `el-table` 表格组件设置引用名为 `productAttrTable`绑定数据为 `list`宽度为 `100%`监听 `selection-change` 事件当表格行选择状态改变时并触发 `handleSelectionChange` 方法根据 `listLoading` 变量控制加载状态显示如显示加载动画等设置表格边框样式用于展示商品属性相关的数据列表 -->
<el-table ref="productAttrTable"
:data="list"
style="width: 100%"
@selection-change="handleSelectionChange"
v-loading="listLoading"
border>
<!-- 表格列类型为 `selection`选择列宽度为 `60px`内容在单元格中居中对齐用于在表格中提供勾选行的功能方便进行批量操作等选择行的场景 -->
<el-table-column type="selection" width="60" align="center"></el-table-column>
<!-- 表格列标签显示为编号宽度为 `100px`内容在单元格中居中对齐通过插槽作用域`slot-scope`获取当前行数据展示商品属性的编号信息 -->
<el-table-column label="编号" width="100" align="center">
<template slot-scope="scope">{{scope.row.id}}</template>
</el-table-column>
<!-- 表格列标签显示为属性名称宽度为 `140px`内容在单元格中居中对齐通过插槽作用域获取当前行数据展示商品属性的名称信息 -->
<el-table-column label="属性名称" width="140" align="center">
<template slot-scope="scope">{{scope.row.name}}</template>
</el-table-column>
<!-- 表格列标签显示为商品类型宽度为 `140px`内容在单元格中居中对齐通过路由对象`$route`的查询参数`query`获取 `cname` 的值来展示商品类型信息表明该属性所属的商品类型 -->
<el-table-column label="商品类型" width="140" align="center">
<template slot-scope="scope">{{$route.query.cname}}</template>
</el-table-column>
<!-- 表格列标签显示为属性是否可选宽度为 `120px`内容在单元格中居中对齐通过管道符`|`使用 `selectTypeFilter` 过滤器对 `scope.row.selectType` 的值进行处理后展示用于以更友好的文字形式展示属性可选类型如单选多选等 -->
<el-table-column label="属性是否可选" width="120" align="center">
<template slot-scope="scope">{{scope.row.selectType|selectTypeFilter}}</template>
</el-table-column>
<!-- 表格列标签显示为属性值的录入方式宽度为 `150px`内容在单元格中居中对齐通过管道符使用 `inputTypeFilter` 过滤器对 `scope.row.inputType` 的值进行处理后展示用于以更友好的文字形式展示属性值录入方式如从列表中选取手工录入等 -->
<el-table-column label="属性值的录入方式" width="150" align="center">
<template slot-scope="scope">{{scope.row.inputType|inputTypeFilter}}</template>
</el-table-column>
<!-- 表格列标签显示为可选值列表内容在单元格中居中对齐通过插槽作用域获取当前行数据直接展示商品属性的可选值列表信息 -->
<el-table-column label="可选值列表" align="center">
<template slot-scope="scope">{{scope.row.inputList}}</template>
</el-table-column>
<!-- 表格列标签显示为排序宽度为 `100px`内容在单元格中居中对齐通过插槽作用域获取当前行数据展示商品属性在排序方面的相关信息比如用于确定属性展示顺序等 -->
<el-table-column label="排序" width="100" align="center">
<template slot-scope="scope">{{scope.row.sort}}</template>
</el-table-column>
<!-- 表格列标签显示为操作宽度为 `200px`内容在单元格中居中对齐通过插槽作用域获取当前行数据包含编辑和删除两个按钮分别点击可触发对应的 `handleUpdate` `handleDelete` 方法用于对单个商品属性进行编辑或删除操作 -->
<el-table-column label="操作" width="200" align="center">
<template slot-scope="scope">
<el-button
size="mini"
@click="handleUpdate(scope.$index, scope.row)">编辑
</el-button>
<el-button
size="mini"
type="danger"
@click="handleDelete(scope.$index, scope.row)">删除
</el-button>
</template>
</el-table-column>
</el-table>
</div>
<!-- div 元素类名为 `batch-operate-container`用于放置批量操作相关的元素如选择批量操作类型的下拉框和执行操作的按钮 -->
<div class="batch-operate-container">
<!-- 使用 Element UI `el-select` 下拉选择框组件尺寸为 `small`小型双向绑定 `operateType` 数据设置占位提示文字为批量操作其下拉选项通过循环 `operates` 数组生成用于选择批量操作的具体类型 -->
<el-select
size="small"
v-model="operateType" placeholder="批量操作">
<el-option
v-for="item in operates"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
<!-- 使用 Element UI `el-button` 按钮组件设置样式 `margin-left: 20px`距左侧 20px 间距类名为 `search-button`点击时触发 `handleBatchOperate` 方法按钮类型为 `primary`主要按钮通常有突出显示效果尺寸为 `small`用于执行确定批量操作的动作 -->
<el-button
style="margin-left: 20px"
class="search-button"
@click="handleBatchOperate()"
type="primary"
size="small">
确定
</el-button>
</div>
<!-- div 元素类名为 `pagination-container`作为分页功能的容器用于包裹 `el-pagination` 分页组件实现数据列表的分页展示 -->
<div class="pagination-container">
<!-- 使用 Element UI `el-pagination` 分页组件设置 `background`背景色样式监听 `size-change`页面尺寸变化 `current-change`当前页码变化事件并分别触发对应的 `handleSizeChange` `handleCurrentChange` 方法设置分页布局样式绑定页面尺寸可选页面尺寸数组当前页码以及总数据量等数据实现数据列表的分页功能 -->
<el-pagination
background
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
layout="total, sizes,prev, pager, next,jumper"
:page-size="listQuery.pageSize"
:page-sizes="[5,10,15]"
:current-page.sync="listQuery.pageNum"
:total="total">
</el-pagination>
</div>
</div>
</template>
<script>
// `@/api/productAttr` `fetchList` `deleteProductAttr` API
import {fetchList, deleteProductAttr} from '@/api/productAttr';
export default {
name: 'productAttrList',
data() {
return {
// `null`
list: null,
// `null`
total: null,
// `true` `false`
listLoading: true,
// `type`
listQuery: {
pageNum: 1,
pageSize: 5,
type: this.$route.query.type
},
// `null`
operateType: null,
// `selection-change` 便
multipleSelection: [],
// `label` `value`
operates: [
{
label: "删除",
value: "deleteProductAttr"
}
]
};
},
created() {
// `getList`
this.getList();
},
methods: {
getList() {
// `listLoading` `true`
this.listLoading = true;
// `fetchList` `cid` ID `listQuery` `listLoading` `false` `list` `total`
fetchList(this.$route.query.cid, this.listQuery).then(response => {
this.listLoading = false;
this.list = response.data.list;
this.total = response.data.total;
});
},
addProductAttr() {
// `$router.push` `/pms/addProductAttr` `cid` `type`
this.$router.push({path:'/pms/addProductAttr',query:{cid:this.$route.query.cid,type:this.$route.query.type}});
},
handleSelectionChange(val) {
// `multipleSelection` 便
this.multipleSelection = val;
},
handleBatchOperate() {
// `multipleSelection` 1 `deleteProductAttr` `id` `handleDeleteProductAttr`
if (this.multipleSelection < 1) {
this.$message({
message: '请选择一条记录',
type: 'warning',
duration: 1000
});
return;
}
if (this.operateType!== 'deleteProductAttr') {
this.$message({
message: '请选择批量操作类型',
type: 'warning',
duration: 1000
});
return;
}
let ids = [];
for (let i = 0; i < this.multipleSelection.length; i++) {
ids.push(this.multipleSelection[i].id);
}
this.handleDeleteProductAttr(ids);
},
handleSizeChange(val) {
// `listQuery` `pageNum` 1 `pageSize` `val` `getList`
this.listQuery.pageNum = 1;
this.listQuery.pageSize = val;
this.getList();
},
handleCurrentChange(val) {
// `listQuery` `pageNum` `val` `getList`
this.listQuery.pageNum = val;
this.getList();
},
handleUpdate(index, row) {
// `$router.push` `/pms/updateProductAttr` `id` ID
this.$router.push({path:'/pms/updateProductAttr',query:{id:row.id}});
},
handleDeleteProductAttr(ids) {
// `URLSearchParams` `ids` `deleteProductAttr` `getList`
this.$confirm('是否要删除该属性', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
let data = new URLSearchParams();
data.append("ids", ids);
deleteProductAttr(data).then(response => {
this.$message({
message: '删除成功',
type: 'success',
duration: 1000
});
this.getList();
});
});
},
handleDelete(index, row) {
// `ids` `id` `handleDeleteProductAttr`
let ids = [];
ids.push(row.id);
this.handleDeleteProductAttr(ids);
}
},
filters:
// `inputType`

@ -0,0 +1,32 @@
<template>
<product-attr-detail :is-edit='true'></product-attr-detail>
</template>
<script>
import ProductAttrDetail from './components/ProductAttrDetail'
export default {
name: 'updateProductAttr',
components: { ProductAttrDetail }
}
</script>
<style scoped>
</style>
#abc
<template>
<!-- 使用名为 `product-attr-detail` 的自定义组件通过 `:is-edit='true'` 属性绑定向该组件传递了名为 `is-edit` 的属性值设为 `true`表示当前处于编辑商品属性的操作模式区别于新增商品属性模式`product-attr-detail` 组件会依据这个属性值来展示对应的编辑界面以及执行相应的编辑相关逻辑 -->
<product-attr-detail :is-edit='true'></product-attr-detail>
</template>
<script>
// `./components/` `ProductAttrDetail`
import ProductAttrDetail from './components/ProductAttrDetail';
export default {
name: 'updateProductAttr',
// `components` `ProductAttrDetail` 使 `updateProductAttr` `<product-attr-detail>` Vue.js 便
components: { ProductAttrDetail }
}
</script>
<style scoped>
/* 这里样式部分设置了 `scoped` 属性,意味着样式只会应用到当前组件内部的元素上。目前该部分样式为空,后续可以根据 `updateProductAttr` 组件的视觉设计要求,添加对应的 CSS 样式规则,像设置组件内文本的字体、颜色,表单元素的布局、边框样式等,让组件在页面中呈现出期望的外观效果 */
</style>

@ -0,0 +1,31 @@
<template> 
<product-cate-detail :is-edit='false'></product-cate-detail>
</template>
<script>
import ProductCateDetail from './components/ProductCateDetail'
export default {
name: 'addProductCate',
components: { ProductCateDetail }
}
</script>
<style>
</style>
#abc
<template>
<!-- 使用名为 `product-cate-detail` 的自定义组件通过 `:is-edit='false'` 属性绑定 `product-cate-detail` 组件传递了 `is-edit` 属性其值为 `false`这表明当前处于添加商品分类的操作模式与编辑商品分类模式相区分`product-cate-detail` 组件会基于这个属性值展示出对应的添加商品分类界面并执行相应的添加相关逻辑 -->
<product-cate-detail :is-edit='false'></product-cate-detail>
</template>
<script>
// `./components/` `ProductCateDetail` `./components/`
import ProductCateDetail from './components/ProductCateDetail';
export default {
name: 'addProductCate',
// `components` `ProductCateDetail` `addProductCate` `<product-cate-detail>` Vue.js 便便
components: { ProductCateDetail }
}
</script>
<style>
/* 这里的样式部分目前为空,后续可以依据 `addProductCate` 组件整体的视觉呈现要求,添加相应的 CSS 样式规则,例如设置组件在页面中的整体布局样式(如宽度、高度、外边距、内边距等),对组件内文本的字体、字号、颜色等样式属性进行定义,还可以针对各种表单元素(如果有的话)设置其边框、背景色、圆角等外观样式,从而让组件在页面中展现出符合预期的视觉效果 */
</style>

@ -0,0 +1,264 @@
<template>
<el-card class="form-container" shadow="never">
<el-form :model="productCate"
:rules="rules"
ref="productCateFrom"
label-width="150px">
<el-form-item label="分类名称:" prop="name">
<el-input v-model="productCate.name"></el-input>
</el-form-item>
<el-form-item label="上级分类:">
<el-select v-model="productCate.parentId"
placeholder="请选择分类">
<el-option
v-for="item in selectProductCateList"
:key="item.id"
:label="item.name"
:value="item.id">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="数量单位:">
<el-input v-model="productCate.productUnit"></el-input>
</el-form-item>
<el-form-item label="排序:">
<el-input v-model="productCate.sort"></el-input>
</el-form-item>
<el-form-item label="是否显示:">
<el-radio-group v-model="productCate.showStatus">
<el-radio :label="1"></el-radio>
<el-radio :label="0"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="是否显示在导航栏:">
<el-radio-group v-model="productCate.navStatus">
<el-radio :label="1"></el-radio>
<el-radio :label="0"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="分类图标:">
<single-upload v-model="productCate.icon"></single-upload>
</el-form-item>
<el-form-item v-for="(filterProductAttr, index) in filterProductAttrList"
:label="index | filterLabelFilter"
:key="filterProductAttr.key"
>
<el-cascader
clearable
v-model="filterProductAttr.value"
:options="filterAttrs">
</el-cascader>
<el-button style="margin-left: 20px" @click.prevent="removeFilterAttr(filterProductAttr)">删除</el-button>
</el-form-item>
<el-form-item>
<el-button size="small" type="primary" @click="handleAddFilterAttr()"></el-button>
</el-form-item>
<el-form-item label="关键词:">
<el-input v-model="productCate.keywords"></el-input>
</el-form-item>
<el-form-item label="分类描述:">
<el-input type="textarea" :autosize="true" v-model="productCate.description"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit('productCateFrom')"></el-button>
<el-button v-if="!isEdit" @click="resetForm('productCateFrom')"></el-button>
</el-form-item>
</el-form>
</el-card>
</template>
<script>
import {fetchList, createProductCate, updateProductCate, getProductCate} from '@/api/productCate';
import {fetchListWithAttr} from '@/api/productAttrCate';
import {getProductAttrInfo} from '@/api/productAttr';
import SingleUpload from '@/components/Upload/singleUpload';
const defaultProductCate = {
description: '',
icon: '',
keywords: '',
name: '',
navStatus: 0,
parentId: 0,
productUnit: '',
showStatus: 0,
sort: 0,
productAttributeIdList: []
};
export default {
name: "ProductCateDetail",
components: {SingleUpload},
props: {
isEdit: {
type: Boolean,
default: false
}
},
data() {
return {
productCate: Object.assign({}, defaultProductCate),
selectProductCateList: [],
rules: {
name: [
{required: true, message: '请输入品牌名称', trigger: 'blur'},
{min: 2, max: 140, message: '长度在 2 到 140 个字符', trigger: 'blur'}
]
},
filterAttrs: [],
filterProductAttrList: [{
value: []
}]
}
},
created() {
if (this.isEdit) {
getProductCate(this.$route.query.id).then(response => {
this.productCate = response.data;
});
getProductAttrInfo(this.$route.query.id).then(response => {
if (response.data != null && response.data.length > 0) {
this.filterProductAttrList = [];
for (let i = 0; i < response.data.length; i++) {
this.filterProductAttrList.push({
key: Date.now() + i,
value: [response.data[i].attributeCategoryId, response.data[i].attributeId]
})
}
}
});
} else {
this.productCate = Object.assign({}, defaultProductCate);
}
this.getSelectProductCateList();
this.getProductAttrCateList();
},
methods: {
getSelectProductCateList() {
fetchList(0, {pageSize: 100, pageNum: 1}).then(response => {
this.selectProductCateList = response.data.list;
this.selectProductCateList.unshift({id: 0, name: '无上级分类'});
});
},
getProductAttrCateList() {
fetchListWithAttr().then(response => {
let list = response.data;
for (let i = 0; i < list.length; i++) {
let productAttrCate = list[i];
let children = [];
if (productAttrCate.productAttributeList != null && productAttrCate.productAttributeList.length > 0) {
for (let j = 0; j < productAttrCate.productAttributeList.length; j++) {
children.push({
label: productAttrCate.productAttributeList[j].name,
value: productAttrCate.productAttributeList[j].id
})
}
}
this.filterAttrs.push({label: productAttrCate.name, value: productAttrCate.id, children: children});
}
});
},
getProductAttributeIdList() {
//
let productAttributeIdList = [];
for (let i = 0; i < this.filterProductAttrList.length; i++) {
let item = this.filterProductAttrList[i];
if (item.value !== null && item.value.length === 2) {
productAttributeIdList.push(item.value[1]);
}
}
return productAttributeIdList;
},
onSubmit(formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
this.$confirm('是否提交数据', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
if (this.isEdit) {
this.productCate.productAttributeIdList = this.getProductAttributeIdList();
updateProductCate(this.$route.query.id, this.productCate).then(response => {
this.$message({
message: '修改成功',
type: 'success',
duration: 1000
});
this.$router.back();
});
} else {
this.productCate.productAttributeIdList = this.getProductAttributeIdList();
createProductCate(this.productCate).then(response => {
this.$refs[formName].resetFields();
this.resetForm(formName);
this.$message({
message: '提交成功',
type: 'success',
duration: 1000
});
});
}
});
} else {
this.$message({
message: '验证失败',
type: 'error',
duration: 1000
});
return false;
}
});
},
resetForm(formName) {
this.$refs[formName].resetFields();
this.productCate = Object.assign({}, defaultProductCate);
this.getSelectProductCateList();
this.filterProductAttrList = [{
value: []
}];
},
removeFilterAttr(productAttributeId) {
if (this.filterProductAttrList.length === 1) {
this.$message({
message: '至少要留一个',
type: 'warning',
duration: 1000
});
return;
}
var index = this.filterProductAttrList.indexOf(productAttributeId);
if (index !== -1) {
this.filterProductAttrList.splice(index, 1)
}
},
handleAddFilterAttr() {
if (this.filterProductAttrList.length === 3) {
this.$message({
message: '最多添加三个',
type: 'warning',
duration: 1000
});
return;
}
this.filterProductAttrList.push({
value: null,
key: Date.now()
});
}
},
filters: {
filterLabelFilter(index) {
if (index === 0) {
return '筛选属性:';
} else {
return '';
}
}
}
}
</script>
<style scoped>
</style>

@ -0,0 +1,456 @@
<template>
<div class="app-container">
<el-card class="operate-container" shadow="never">
<i class="el-icon-tickets" style="margin-top: 5px"></i>
<span style="margin-top: 5px">数据列表</span>
<el-button
class="btn-add"
@click="handleAddProductCate()"
size="mini">
添加
</el-button>
</el-card>
<div class="table-container">
<el-table ref="productCateTable"
style="width: 100%"
:data="list"
v-loading="listLoading" border>
<el-table-column label="编号" width="100" align="center">
<template slot-scope="scope">{{scope.row.id}}</template>
</el-table-column>
<el-table-column label="分类名称" align="center">
<template slot-scope="scope">{{scope.row.name}}</template>
</el-table-column>
<el-table-column label="级别" width="100" align="center">
<template slot-scope="scope">{{scope.row.level | levelFilter}}</template>
</el-table-column>
<el-table-column label="商品数量" width="100" align="center">
<template slot-scope="scope">{{scope.row.productCount }}</template>
</el-table-column>
<el-table-column label="数量单位" width="100" align="center">
<template slot-scope="scope">{{scope.row.productUnit }}</template>
</el-table-column>
<el-table-column label="导航栏" width="100" align="center">
<template slot-scope="scope">
<el-switch
@change="handleNavStatusChange(scope.$index, scope.row)"
:active-value="1"
:inactive-value="0"
v-model="scope.row.navStatus">
</el-switch>
</template>
</el-table-column>
<el-table-column label="是否显示" width="100" align="center">
<template slot-scope="scope">
<el-switch
@change="handleShowStatusChange(scope.$index, scope.row)"
:active-value="1"
:inactive-value="0"
v-model="scope.row.showStatus">
</el-switch>
</template>
</el-table-column>
<el-table-column label="排序" width="100" align="center">
<template slot-scope="scope">{{scope.row.sort }}</template>
</el-table-column>
<el-table-column label="设置" width="200" align="center">
<template slot-scope="scope">
<el-button
size="mini"
:disabled="scope.row.level | disableNextLevel"
@click="handleShowNextLevel(scope.$index, scope.row)">查看下级
</el-button>
<el-button
size="mini"
@click="handleTransferProduct(scope.$index, scope.row)">转移商品
</el-button>
</template>
</el-table-column>
<el-table-column label="操作" width="200" align="center">
<template slot-scope="scope">
<el-button
size="mini"
@click="handleUpdate(scope.$index, scope.row)">编辑
</el-button>
<el-button
size="mini"
type="danger"
@click="handleDelete(scope.$index, scope.row)">删除
</el-button>
</template>
</el-table-column>
</el-table>
</div>
<div class="pagination-container">
<el-pagination
background
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
layout="total, sizes,prev, pager, next,jumper"
:page-size="listQuery.pageSize"
:page-sizes="[5,10,15]"
:current-page.sync="listQuery.pageNum"
:total="total">
</el-pagination>
</div>
</div>
</template>
<script>
import {fetchList,deleteProductCate,updateShowStatus,updateNavStatus} from '@/api/productCate'
export default {
name: "productCateList",
data() {
return {
list: null,
total: null,
listLoading: true,
listQuery: {
pageNum: 1,
pageSize: 5
},
parentId: 0
}
},
created() {
this.resetParentId();
this.getList();
},
watch: {
$route(route) {
this.resetParentId();
this.getList();
}
},
methods: {
resetParentId(){
this.listQuery.pageNum = 1;
if (this.$route.query.parentId != null) {
this.parentId = this.$route.query.parentId;
} else {
this.parentId = 0;
}
},
handleAddProductCate() {
this.$router.push('/pms/addProductCate');
},
getList() {
this.listLoading = true;
fetchList(this.parentId, this.listQuery).then(response => {
this.listLoading = false;
this.list = response.data.list;
this.total = response.data.total;
});
},
handleSizeChange(val) {
this.listQuery.pageNum = 1;
this.listQuery.pageSize = val;
this.getList();
},
handleCurrentChange(val) {
this.listQuery.pageNum = val;
this.getList();
},
handleNavStatusChange(index, row) {
let data = new URLSearchParams();
let ids=[];
ids.push(row.id)
data.append('ids',ids);
data.append('navStatus',row.navStatus);
updateNavStatus(data).then(response=>{
this.$message({
message: '修改成功',
type: 'success',
duration: 1000
});
});
},
handleShowStatusChange(index, row) {
let data = new URLSearchParams();
let ids=[];
ids.push(row.id)
data.append('ids',ids);
data.append('showStatus',row.showStatus);
updateShowStatus(data).then(response=>{
this.$message({
message: '修改成功',
type: 'success',
duration: 1000
});
});
},
handleShowNextLevel(index, row) {
this.$router.push({path: '/pms/productCate', query: {parentId: row.id}})
},
handleTransferProduct(index, row) {
console.log('handleAddProductCate');
},
handleUpdate(index, row) {
this.$router.push({path:'/pms/updateProductCate',query:{id:row.id}});
},
handleDelete(index, row) {
this.$confirm('是否要删除该品牌', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
deleteProductCate(row.id).then(response => {
this.$message({
message: '删除成功',
type: 'success',
duration: 1000
});
this.getList();
});
});
}
},
filters: {
levelFilter(value) {
if (value === 0) {
return '一级';
} else if (value === 1) {
return '二级';
}
},
disableNextLevel(value) {
if (value === 0) {
return false;
} else {
return true;
}
}
}
}
</script>
<style scoped>
</style>
#abc
<template>
<!-- 最外层的 `div` 元素设置了类名为 `app-container`作为整个页面内容的容器用于包裹内部各个功能模块实现整体页面布局 -->
<div class="app-container">
<!-- 使用 Element UI `el-card` 组件创建一个卡片式容器类名为 `operate-container`设置阴影效果为 `never`无阴影用于放置与数据列表操作相关的元素比如操作按钮等 -->
<el-card class="operate-container" shadow="never">
<!-- 使用 Element UI 的图标组件展示票务图标并设置 `margin-top: 5px`使其在垂直方向上距顶部有一定间距在视觉上辅助提示该区域与数据操作相关 -->
<i class="el-icon-tickets" style="margin-top: 5px"></i>
<!-- 显示数据列表文字同样设置 `margin-top: 5px`明确该卡片容器内的操作是针对下方展示的数据列表起到功能说明作用 -->
<span style="margin-top: 5px">数据列表</span>
<!-- 使用 Element UI `el-button` 按钮组件设置类名为 `btn-add`点击时触发 `handleAddProductCate` 方法尺寸为 `mini`迷你型用于执行添加商品分类的操作 -->
<el-button
class="btn-add"
@click="handleAddProductCate()"
size="mini">
添加
</el-button>
</el-card>
<!-- `div` 元素类名为 `table-container`作为表格展示的容器用于包裹 `el-table` 组件使其在页面中以特定样式展示数据列表 -->
<div class="table-container">
<!-- 使用 Element UI `el-table` 表格组件设置引用名为 `productCateTable`宽度为 `100%`绑定数据为 `list`根据 `v-loading` 指令结合 `listLoading` 变量控制加载状态显示如显示加载动画等设置表格边框样式用于展示商品分类相关的数据列表 -->
<el-table ref="productCateTable"
style="width: 100%"
:data="list"
v-loading="listLoading" border>
<!-- 表格列标签显示为编号宽度为 `100px`内容在单元格中居中对齐通过插槽作用域`slot-scope`获取当前行数据展示商品分类的编号信息 -->
<el-table-column label="编号" width="100" align="center">
<template slot-scope="scope">{{scope.row.id}}</template>
</el-table-column>
<!-- 表格列标签显示为分类名称内容在单元格中居中对齐通过插槽作用域获取当前行数据展示商品分类的名称信息 -->
<el-table-column label="分类名称" align="center">
<template slot-scope="scope">{{scope.row.name}}</template>
</el-table-column>
<!-- 表格列标签显示为级别宽度为 `100px`内容在单元格中居中对齐通过管道符`|`使用 `levelFilter` 过滤器对 `scope.row.level` 的值进行处理后展示用于以更友好的文字形式一级二级展示商品分类的级别信息 -->
<el-table-column label="级别" width="100" align="center">
<template slot-scope="scope">{{scope.row.level | levelFilter}}</template>
</el-table-column>
<!-- 表格列标签显示为商品数量宽度为 `100px`内容在单元格中居中对齐通过插槽作用域获取当前行数据展示该商品分类下包含的商品数量信息 -->
<el-table-column label="商品数量" width="100" align="center">
<template slot-scope="scope">{{scope.row.productCount }}</template>
</el-table-column>
<!-- 表格列标签显示为数量单位宽度为 `100px`内容在单元格中居中对齐通过插槽作用域获取当前行数据展示商品数量对应的单位信息 -->
<el-table-column label="数量单位" width="100" align="center">
<template slot-scope="scope">{{scope.row.productUnit }}</template>
</el-table-column>
<!-- 表格列标签显示为导航栏宽度为 `100px`内容在单元格中居中对齐通过插槽作用域获取当前行数据包含一个 `el-switch` 开关组件监听开关状态变化事件 `change` 并触发 `handleNavStatusChange` 方法双向绑定 `scope.row.navStatus` 数据用于控制商品分类在导航栏的显示状态如是否展示在导航栏等 -->
<el-table-column label="导航栏" width="100" align="center">
<template slot-scope="scope">
<el-switch
@change="handleNavStatusChange(scope.$index, scope.row)"
:active-value="1"
:inactive-value="0"
v-model="scope.row.navStatus">
</el-switch>
</template>
</el-table-column>
<!-- 表格列标签显示为是否显示宽度为 `100px`内容在单元格中居中对齐通过插槽作用域获取当前行数据包含一个 `el-switch` 开关组件监听开关状态变化事件 `change` 并触发 `handleShowStatusChange` 方法双向绑定 `scope.row.showStatus` 数据用于控制商品分类是否显示可能在页面其他位置的显示与否等情况 -->
<el-table-column label="是否显示" width="100" align="center">
<template slot-scope="scope">
<el-switch
@change="handleShowStatusChange(scope.$index, scope.row)"
:active-value="1"
:inactive-value="0"
v-model="scope.row.showStatus">
</el-switch>
</template>
</el-table-column>
<!-- 表格列标签显示为排序宽度为 `100px`内容在单元格中居中对齐通过插槽作用域获取当前行数据展示商品分类在排序方面的相关信息比如用于确定分类展示顺序等 -->
<el-table-column label="排序" width="100" align="center">
<template slot-scope="scope">{{scope.row.sort }}</template>
</el-table-column>
<!-- 表格列标签显示为设置宽度为 `200px`内容在单元格中居中对齐通过插槽作用域获取当前行数据包含两个按钮查看下级按钮根据 `scope.row.level` 通过 `disableNextLevel` 过滤器判断是否禁用如果是一级分类可能允许查看下级等逻辑点击触发 `handleShowNextLevel` 方法转移商品按钮点击触发 `handleTransferProduct` 方法用于相关的商品转移操作目前方法内仅打印日志可能后续完善具体逻辑 -->
<el-table-column label="设置" width="200" align="center">
<template slot-scope="scope">
<el-button
size="mini"
:disabled="scope.row.level | disableNextLevel"
@click="handleShowNextLevel(scope.$index, scope.row)">查看下级</ </el-button>
<el-button
size="mini"
@click="handleTransferProduct(scope.$index, scope.row)">转移商品</ </el-button>
</template>
</el-table-column>
<!-- 表格列标签显示为操作宽度为 `200px`内容在单元格中居中对齐通过插槽作用域获取当前行数据包含编辑和删除两个按钮分别点击可触发对应的 `handleUpdate` `handleDelete` 方法用于对单个商品分类进行编辑或删除操作 -->
<el-table-column label="操作" width="200" align="center">
<template slot-scope="scope">
<el-button
size="mini"
@click="handleUpdate(scope.$index, scope.row)">编辑</ </el-button>
<el-button
size="mini"
type="danger"
@click="handleDelete(scope.$index, scope.row)">删除</ </el-button>
</template>
</el-table-column>
</el-table>
</div>
<!-- `div` 元素类名为 `pagination-container`作为分页功能的容器用于包裹 `el-pagination` 分页组件实现数据列表的分页展示 -->
<div class="pagination-container">
<!-- 使用 Element UI `el-pagination` 分页组件设置 `background`背景色样式监听 `size-change`页面尺寸变化 `current-change`当前页码变化事件并分别触发对应的 `handleSizeChange` `handleCurrentChange` 方法设置分页布局样式绑定页面尺寸可选页面尺寸数组当前页码以及总数据量等数据实现数据列表的分页功能 -->
<el-pagination
background
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
layout="total, sizes,prev, pager, next,jumper"
:page-size="listQuery.pageSize"
:page-sizes="[5,10,15]"
:current-page.sync="listQuery.pageNum"
:total="total">
</el-pagination>
</div>
</div>
</template>
<script>
// `@/api/productCate` `fetchList``deleteProductCate``updateShowStatus``updateNavStatus` API
import {fetchList,deleteProductCate,updateShowStatus,updateNavStatus} from '@/api/productCate';
export default {
name: "productCateList",
data() {
return {
// `null`
list: null,
// `null`
total: null,
// `true` `false`
listLoading: true,
//
listQuery: {
pageNum: 1,
pageSize: 5
},
// ID `0`
parentId: 0
};
},
created() {
// `resetParentId` ID `getList`
this.resetParentId();
this.getList();
},
watch: {
// `$route` `resetParentId` ID `getList`
$route(route) {
this.resetParentId();
this.getList();
}
},
methods: {
resetParentId() {
// `listQuery` `pageNum` `1` `parentId` `null` `parentId` `parentId` `null` `parentId` `0` ID
this.listQuery.pageNum = 1;
if (this.$route.query.parentId!= null) {
this.parentId = this.$route.query.parentId;
} else {
this.parentId = 0;
}
},
handleAddProductCate() {
// `$router.push` `/pms/addProductCate`
this.$router.push('/pms/addProductCate');
},
getList() {
// `listLoading` `true`
this.listLoading = true;
// `fetchList` `parentId` ID `listQuery` `listLoading` `false` `list` `total`
fetchList(this.parentId, this.listQuery).then(response => {
this.listLoading = false;
this.list = response.data.list;
this.total = response.data.total;
});
},
handleSizeChange(val) {
// `listQuery` `pageNum` `1` `pageSize` `val` `getList`
this.listQuery.pageNum = 1;
this.listQuery.pageSize = val;
this.getList();
},
handleCurrentChange(val) {
// `listQuery` `pageNum` `val` `getList`
this.listQuery.pageNum = val;
this.getList();
},
handleNavStatusChange(index, row) {
// `URLSearchParams` `id` `ids` `navStatus` `updateNavStatus`
let data = new URLSearchParams();
let ids=[];
ids.push(row.id)
data.append('ids',ids);
data.append('navStatus',row.navStatus);
updateNavStatus(data).then(response=>{
this.$message({
message: '修改成功',
type: 'success',
duration: 1000
});
});
},
handleShowStatusChange(index, row) {
// `URLSearchParams` `id` `ids` `showStatus` `updateShowStatus`
let data = new URLSearchParams();
let ids=[];
ids.push(row.id)
data.append('ids',ids);
data.append('showStatus',row.showStatus);
updateShowStatus(data).then(response=>{
this.$message({
message: '修改成功',
type: 'success',
duration: 1000
});
});
},
handleShowNextLevel(index, row) {
// `$router.push` `/pms/productCate` `parentId` `id`
this.$router.push({path: '/pms/productCate', query: {parentId: row.id}})
},
handleTransferProduct(index, row) {
// `handleAddProductCate`
console.log('handleAddProductCate');
},
handleUpdate(index, row)
// `$router.push

@ -0,0 +1,31 @@
<template> 
<product-cate-detail :is-edit='true'></product-cate-detail>
</template>
<script>
import ProductCateDetail from './components/ProductCateDetail'
export default {
name: 'updateProductCate',
components: { ProductCateDetail }
}
</script>
<style>
</style>
#abc
<template>
<!-- 使用名为 `product-cate-detail` 的自定义组件并通过 `:is-edit='true'` 进行属性绑定传递了 `is-edit` 属性且其值设为 `true`这意味着当前处于编辑商品分类的操作模式与新增商品分类的模式相区别`product-cate-detail` 组件会依据这个属性值来展示对应的编辑界面以及执行相应的编辑相关逻辑例如展示已有的商品分类信息以供修改并处理保存修改后的数据等操作 -->
<product-cate-detail :is-edit='true'></product-cate-detail>
</template>
<script>
// `./components/` `ProductCateDetail` `./components/`
import ProductCateDetail from './components/ProductCateDetail';
export default {
name: 'updateProductCate',
// `components` `ProductCateDetail` 使 `updateProductCate` `<product-cate-detail>` Vue.js 便
components: { ProductCateDetail }
}
</script>
<style>
/* 这里的样式部分目前为空,后续可以根据 `updateProductCate` 组件整体的视觉呈现要求,添加相应的 CSS 样式规则。例如,可以设置组件在页面中的整体布局样式(如宽度、高度、外边距、内边距等),对组件内文本的字体、字号、颜色等样式属性进行定义,还能针对各种表单元素(如果有的话)设置其边框、背景色、圆角等外观样式,从而让组件在页面中展现出符合预期的视觉效果 */
</style>
Loading…
Cancel
Save