Initial commit

This commit is contained in:
南宁网宿科技
2023-06-03 23:57:39 +08:00
commit 13bed2bafb
1017 changed files with 175796 additions and 0 deletions
+433
View File
@@ -0,0 +1,433 @@
<template>
<div>
<a-form-item label="商品规格" :labelCol="labelCol" :wrapperCol="wrapperCol">
<div v-if="true" class="form-item-help" style="line-height: 36px">
<small>最多添加3个商品规格组,生成的SKU数量不能超出50个</small>
</div>
<!-- 规格组 -->
<div
class="spec-group"
>
<div class="spec-group-item clearfix">
<a-input
class="group-item-input"
:readOnly="isSpecLocked"
placeholder="请输入规格名称"
@change="onChangeSpecGroupIpt"
/>
<a
v-if="!isSpecLocked"
class="group-item-delete"
href="javascript:;"
@click="handleDeleteSpecGroup(index)"
>删除规格组</a
>
</div>
<div class="spec-value clearfix">
<div
class="spec-value-item"
>
<a-input
class="value-item-input"
:readOnly="isSpecLocked"
placeholder="请输入规格值"
@change="onChangeSpecValueIpt"
/>
<a-icon
v-if="!isSpecLocked"
class="icon-close"
theme="filled"
type="close-circle"
@click="handleDeleteSpecValue(index, idx)"
/>
</div>
<div v-if="!isSpecLocked" class="spec-value-add">
<a
class="group-item-delete"
href="javascript:;"
@click="handleAddSpecValue(index)"
>新增规格值</a
>
</div>
</div>
</div>
<!-- 添加规格按钮 -->
<a-button
v-if="!isSpecLocked && multiSpecData.specList.length < 3"
class="spec-group-add-btn"
icon="plus"
@click="handleAddSpecGroup"
>添加规格组</a-button
>
</a-form-item>
<a-form-item
v-show="multiSpecData.skuList.length"
label="SKU列表"
:labelCol="labelCol"
:wrapperCol="wrapperCol"
>
<!-- 批量设置 -->
<div v-if="multiSpecData.skuList.length > 1" class="sku-batch">
<span class="title">批量设置:</span>
<a-input-number
placeholder="商品价格"
:min="0.01"
:precision="2"
/>
<a-input-number
placeholder="成本价格"
:min="0"
:precision="2"
/>
<a-input-number
placeholder="库存数量"
:min="0"
:precision="0"
/>
<a-input-number
placeholder="商品重量"
:min="0"
:precision="2"
/>
<a-input
placeholder="sku编码"
/>
<a-button @click="handleSkuBatch">立即设置</a-button>
</div>
<!-- sku列表table -->
<a-table
class="sku-list"
:columns="multiSpecData.skuColumns"
:dataSource="multiSpecData.skuList"
:scroll="{ x: true }"
:pagination="false"
bordered
>
<!-- 预览图 -->
<template #image="text, item">
<SelectImage
v-model="item.image_id"
:defaultList="item.image_id > 0 && item.image ? [item.image] : []"
:width="50"
/>
</template>
<!-- 商品价格 -->
<template #goods_price="text, item">
<a-input-number
v-model="item.goods_price"
size="small"
:min="0.01"
:precision="2"
/>
</template>
<!-- 成本价格 -->
<template #line_price="text, item">
<a-input-number
v-model="item.line_price"
size="small"
:min="0"
:precision="2"
/>
</template>
<!-- 库存数量 -->
<template #stock_num="text, item">
<a-input-number
v-model="item.stock_num"
size="small"
:min="0"
:precision="0"
/>
</template>
<!-- 商品重量 -->
<template #goods_weight="text, item">
<a-input-number
v-model="item.goods_weight"
size="small"
:min="0"
:precision="2"
/>
</template>
<!-- sku编码 -->
<template #goods_sku_no="text, item">
<a-input v-model="item.goods_sku_no" size="small" />
</template>
</a-table>
</a-form-item>
</div>
</template>
<script>
import PropTypes from 'ant-design-vue/es/_util/vue-types';
import MultiSpecModel from '@/common/model/goods/MultiSpec';
// import { SelectImage } from '@/components';
export default {
components: {
// SelectImage
},
props: {
// 默认的规格列表
defaultSpecList: PropTypes.array.def([]),
// 默认的SKU列表
defaultSkuList: PropTypes.array.def([]),
// 商品规格是否锁定(锁定状态下不允许编辑规格)
isSpecLocked: PropTypes.bool.def(false)
},
data() {
return {
// 标签布局属性
labelCol: { span: 3 },
// 输入框布局属性
wrapperCol: { span: 21 },
// 商品多规格模型
MultiSpecModel: new MultiSpecModel(),
// MultiSpecModel: Object,
multiSpecData: {
// 规格列表
specList: [],
// SKU列表
skuList: []
}
};
},
watch: {
defaultSpecList(val) {
if (val.length && this.MultiSpecModel.isEmpty()) {
this.getData();
}
}
},
// 初始化数据
created() {
// 获取规格及SKU信息
this.getData();
},
methods: {
// 获取规格及SKU信息(展示)
getData() {
const { defaultSpecList, defaultSkuList } = this;
this.multiSpecData = this.MultiSpecModel.getData(
defaultSpecList,
defaultSkuList
);
},
// 获取规格及SKU信息(表单提交)
getFromSpecData() {
return this.MultiSpecModel.getFromSpecData();
},
// 添加规格组
handleAddSpecGroup() {
if (this.checkSkuMaxNum()) {
this.MultiSpecModel.handleAddSpecGroup();
}
},
// 删除规格组
handleDeleteSpecGroup(groupIndex) {
const app = this;
const modal = this.$confirm({
title: '您确定要删除该规格组吗?',
content: '删除后不可恢复',
onOk() {
// 删除元素
app.MultiSpecModel.handleDeleteSpecGroup(groupIndex);
// 关闭对话框
modal.destroy();
}
});
},
// 新增规格值
handleAddSpecValue(groupIndex) {
if (this.checkSkuMaxNum()) {
this.MultiSpecModel.handleAddSpecValue(groupIndex);
}
},
// 删除规格值
handleDeleteSpecValue(groupIndex, valueIndex) {
const app = this;
const modal = this.$confirm({
title: '您确定要删除该规格值吗?',
content: '删除后不可恢复',
onOk() {
// 删除元素
app.MultiSpecModel.handleDeleteSpecValue(groupIndex, valueIndex);
// 关闭对话框
modal.destroy();
}
});
},
// 规格组输入框change事件
onChangeSpecGroupIpt() {
// 更新skuList
this.MultiSpecModel.onUpdate(true);
},
// 规格值输入框change事件
onChangeSpecValueIpt(event, itm) {
// 更新skuList
this.MultiSpecModel.onUpdate(true);
},
// 验证最大sku数量
checkSkuMaxNum() {
const skuList = this.multiSpecData.skuList;
if (skuList.length >= 50) {
this.$message.error(
`生成的sku列表数量不能大于50个,当前数量:${skuList.length}个`,
2.5
);
return false;
}
return true;
},
// 批量设置sku事件
handleSkuBatch() {
this.MultiSpecModel.handleSkuBatch();
},
// 验证多规格表单
verifyForm() {
if (!this.MultiSpecModel.verifyForm()) {
this.$message.error(this.MultiSpecModel.getError(), 2);
return false;
}
return true;
}
}
};
</script>
<style lang="less" scoped>
// 商品多规格
.spec-group {
width: 895px;
margin-bottom: 15px;
line-height: normal;
.ant-input {
height: 28px;
padding: 4px 11px;
font-size: @font-size-base;
line-height: 1.5;
&::placeholder {
font-size: @font-size-base;
}
}
.form-item-help {
line-height: 36px;
}
.spec-group-item {
background: #f4f5f9;
padding: 7px 12px;
line-height: 28px;
.group-item-input {
float: left;
width: 180px;
}
.group-item-delete {
font-size: 12px;
float: right;
}
}
.spec-value {
background: #fbfbfb;
padding: 8px 35px;
.spec-value-add,
.spec-value-item {
float: left;
margin-bottom: 6px;
margin-top: 6px;
}
.spec-value-add {
height: 28px;
line-height: 28px;
a {
font-size: 12px;
}
}
.spec-value-item {
position: relative;
margin-right: 20px;
.value-item-input {
width: 186px;
}
&:hover {
.icon-close {
display: block;
}
}
.icon-close {
display: none;
position: absolute;
top: -8px;
right: -8px;
cursor: pointer;
font-size: 16px;
color: #7d7d7d;
}
}
}
}
// 添加规格按钮
.spec-group-add-btn {
font-size: @font-size-base;
}
// 批量设置sku
.sku-batch {
line-height: 40px;
margin-bottom: 12px;
.title {
line-height: 28px;
margin-right: 15px;
font-size: @font-size-base;
}
/deep/.ant-input,
/deep/.ant-input-number {
width: 120px;
margin-right: 15px;
}
/deep/.ant-input {
width: 140px;
}
}
// sku列表
.sku-list {
width: 895px;
/deep/.ant-table-thead > tr > th,
/deep/.ant-table-tbody > tr > td {
white-space: nowrap;
}
/deep/.ant-table-tbody > tr > td {
padding: 12px 18px;
}
/deep/.ant-input-sm,
/deep/.ant-input-number-sm {
height: 28px;
}
/deep/.ant-input-number-sm input {
height: 26px;
}
}
</style>
@@ -0,0 +1,39 @@
<!-- 目录选择下拉框 -->
<template>
<a-tree-select
allow-clear
tree-default-expand-all
:placeholder="placeholder"
:value="value || undefined"
:tree-data="data"
:dropdown-style="{ maxHeight: '360px', overflow: 'auto' }"
@update:value="updateValue"
/>
</template>
<script lang="ts" setup>
import type { Goods } from '@/api/goods/model';
const emit = defineEmits<{
(e: 'update:value', value?: number): void;
}>();
withDefaults(
defineProps<{
// 选中的数据(v-modal)
value?: number;
// 提示信息
placeholder?: string;
// 文章分类
data: Goods[];
}>(),
{
placeholder: '请选择上级分类'
}
);
/* 更新选中数据 */
const updateValue = (value?: number) => {
emit('update:value', value);
};
</script>
@@ -0,0 +1,45 @@
<!-- 选择下拉框 -->
<template>
<a-select
show-search
optionFilterProp="label"
:options="data"
allow-clear
:value="value"
:placeholder="placeholder"
@update:value="updateValue"
@blur="onBlur"
/>
</template>
<script lang="ts" setup>
import { getDictionaryOptions } from '@/utils/common';
const emit = defineEmits<{
(e: 'update:value', value: string): void;
(e: 'blur'): void;
}>();
withDefaults(
defineProps<{
value?: string;
placeholder?: string;
}>(),
{
placeholder: '请选择服务器配置'
}
);
// 字典数据
const data = getDictionaryOptions('serverConfiguration');
/* 更新选中数据 */
const updateValue = (value: string) => {
emit('update:value', value);
};
/* 失去焦点 */
const onBlur = () => {
emit('blur');
};
</script>
@@ -0,0 +1,51 @@
<!-- 选择下拉框 -->
<template>
<a-select
optionFilterProp="label"
:options="data"
allow-clear
:value="value"
:placeholder="placeholder"
@update:value="updateValue"
@blur="onBlur"
@change="onChange"
/>
</template>
<script lang="ts" setup>
import { getDictionaryOptions } from '@/utils/common';
const emit = defineEmits<{
(e: 'update:value', value: string): void;
(e: 'blur'): void;
(e: 'change'): void;
}>();
withDefaults(
defineProps<{
value?: string;
placeholder?: string;
}>(),
{
placeholder: '请选择客户跟进状态'
}
);
// 字典数据
const data = getDictionaryOptions('customerFollowStatus');
/* 更新选中数据 */
const updateValue = (value: string) => {
emit('update:value', value);
};
/* 失去焦点 */
const onBlur = () => {
emit('blur');
};
/* 选择事件 */
const onChange = (e) => {
emit('change', e);
};
</script>
@@ -0,0 +1,70 @@
<!-- 角色选择下拉框 -->
<template>
<a-select
optionFilterProp="label"
:options="data"
allow-clear
:value="value"
:placeholder="placeholder"
@update:value="updateValue"
@blur="onBlur"
@change="onChange"
/>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { message } from 'ant-design-vue';
import { listDictionaryData } from '@/api/system/dictionary-data';
import type { SelectProps } from 'ant-design-vue';
const emit = defineEmits<{
(e: 'update:value', value: string): void;
(e: 'blur'): void;
(e: 'change'): void;
}>();
withDefaults(
defineProps<{
value?: string;
placeholder?: string;
}>(),
{
placeholder: '请选择客户来源'
}
);
// 字典数据
const data = ref<SelectProps['options']>([]);
/* 更新选中数据 */
const updateValue = (value: string) => {
emit('update:value', value);
};
/* 获取字典数据 */
listDictionaryData({
dictCode: 'customerSource'
})
.then((list) => {
data.value = list.map((d) => {
return {
value: d.dictDataCode,
label: d.dictDataName
};
});
})
.catch((e) => {
message.error(e.message);
});
/* 失去焦点 */
const onBlur = () => {
emit('blur');
};
/* 选择事件 */
const onChange = (e) => {
emit('change', e);
};
</script>
@@ -0,0 +1,45 @@
<!-- 角色选择下拉框 -->
<template>
<a-select
show-search
optionFilterProp="label"
:options="data"
allow-clear
:value="value"
:placeholder="placeholder"
@update:value="updateValue"
@blur="onBlur"
/>
</template>
<script lang="ts" setup>
import { getDictionaryOptions } from '@/utils/common';
const emit = defineEmits<{
(e: 'update:value', value: string): void;
(e: 'blur'): void;
}>();
withDefaults(
defineProps<{
value?: string;
placeholder?: string;
}>(),
{
placeholder: '请选择状态'
}
);
// 字典数据
const data = getDictionaryOptions('status');
/* 更新选中数据 */
const updateValue = (value: string) => {
emit('update:value', value);
};
/* 失去焦点 */
const onBlur = () => {
emit('blur');
};
</script>
@@ -0,0 +1,63 @@
<!-- 角色选择下拉框 -->
<template>
<a-select
optionFilterProp="label"
:options="data"
allow-clear
:value="value"
:placeholder="placeholder"
@update:value="updateValue"
@blur="onBlur"
/>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { message } from 'ant-design-vue';
import { listDictionaryData } from '@/api/system/dictionary-data';
import type { SelectProps } from 'ant-design-vue';
const emit = defineEmits<{
(e: 'update:value', value: string): void;
(e: 'blur'): void;
}>();
withDefaults(
defineProps<{
value?: string;
placeholder?: string;
}>(),
{
placeholder: '请选择客户类型'
}
);
// 字典数据
const data = ref<SelectProps['options']>([]);
/* 更新选中数据 */
const updateValue = (value: string) => {
emit('update:value', value);
};
/* 获取字典数据 */
listDictionaryData({
dictCode: 'customerType'
})
.then((list) => {
data.value = list.map((d) => {
return {
value: d.dictDataCode,
label: d.dictDataName
};
});
})
.catch((e) => {
message.error(e.message);
});
/* 失去焦点 */
const onBlur = () => {
emit('blur');
};
</script>
@@ -0,0 +1,76 @@
<!-- 角色选择下拉框 -->
<template>
<a-select
show-search
optionFilterProp="label"
:options="data"
allow-clear
:value="value"
:placeholder="placeholder"
@update:value="updateValue"
@search="onSearch"
@blur="onBlur"
/>
</template>
<script lang="ts" setup>
import { reactive, ref } from 'vue';
import { message } from 'ant-design-vue';
import { listUsers } from '@/api/system/user';
import type { SelectProps } from 'ant-design-vue';
import { UserParam } from '@/api/system/user/model';
const emit = defineEmits<{
(e: 'update:value', value: string): void;
(e: 'blur'): void;
}>();
withDefaults(
defineProps<{
value?: string;
placeholder?: string;
}>(),
{
placeholder: '请选择订单类型'
}
);
// 字典数据
const data = ref<SelectProps['options']>([]);
/* 更新选中数据 */
const updateValue = (value: string) => {
emit('update:value', value);
};
// 默认搜索条件
const where = ref<UserParam>({});
const search = () => {
/* 获取用户列 */
listUsers({ ...where?.value })
.then((result) => {
data.value = result?.map((d) => {
return {
value: d.userId,
label: d.nickname
};
});
})
.catch((e) => {
message.error(e.message);
});
};
const onSearch = (e) => {
where.value.nickname = e;
search();
};
search();
/* 失去焦点 */
const onBlur = () => {
emit('blur');
};
</script>
@@ -0,0 +1,70 @@
<!-- 选择下拉框 -->
<template>
<a-select
show-search
optionFilterProp="label"
:options="data"
allow-clear
:value="value"
:placeholder="placeholder"
@update:value="updateValue"
@blur="onBlur"
@search="onSearch"
/>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { message } from 'ant-design-vue';
import { pageCustomer } from '@/api/oa/customer';
import type { SelectProps } from 'ant-design-vue';
const emit = defineEmits<{
(e: 'update:value', value: string): void;
(e: 'blur'): void;
}>();
withDefaults(
defineProps<{
value?: number;
placeholder?: string;
}>(),
{
placeholder: '请选择所属客户'
}
);
// 字典数据
const data = ref<SelectProps['options']>([]);
/* 更新选中数据 */
const updateValue = (value: string) => {
emit('update:value', value);
};
/* 获取客户列表数据 */
pageCustomer({ progress: '4' })
.then((result) => {
data.value = result?.list.map((d) => {
return {
value: d.customerId,
label: d.customerName
};
});
})
.catch((e) => {
message.error(e.message);
});
/* 失去焦点 */
const onBlur = () => {
emit('blur');
};
// 联动搜索
const onSearch = (customerName) => {
pageCustomer({ customerName }).then((res) => {
console.log(res);
});
};
</script>
@@ -0,0 +1,53 @@
<!-- 选择下拉框 -->
<template>
<a-select
show-search
optionFilterProp="label"
:options="data"
allow-clear
:value="value"
:placeholder="placeholder"
@update:value="updateValue"
@blur="onBlur"
/>
</template>
<script lang="ts" setup>
import { getDictionaryOptions } from '@/utils/common';
import { ref } from "vue";
import { SelectProps } from "ant-design-vue";
const emit = defineEmits<{
(e: 'update:value', value: string): void;
(e: 'blur'): void;
}>();
withDefaults(
defineProps<{
value?: string;
placeholder?: string;
}>(),
{
placeholder: '请选择运费模板'
}
);
// 字典数据
const data = ref<SelectProps['options']>([
{
label: '全国包邮(除偏远地区)',
text: '全国包邮(除偏远地区)',
value: 0
}
]);
/* 更新选中数据 */
const updateValue = (value: string) => {
emit('update:value', value);
};
/* 失去焦点 */
const onBlur = () => {
emit('blur');
};
</script>
@@ -0,0 +1,648 @@
<!-- 用户编辑弹窗 -->
<template>
<ele-modal
:width="880"
:visible="visible"
:confirm-loading="loading"
:maxable="maxAble"
:title="isUpdate ? '编辑商品' : '添加商品'"
:body-style="{ paddingBottom: '8px' }"
@update:visible="updateVisible"
:maskClosable="false"
@ok="save"
>
<a-tabs v-model:activeKey="activeKey" type="card">
<a-tab-pane key="1" tab="基本信息">
<a-form
ref="formRef"
:model="form"
:rules="rules"
:label-col="{ md: { span: 8 }, sm: { span: 24 } }"
:wrapper-col="{ md: { span: 24 }, sm: { span: 24 } }"
layout="vertical"
>
<a-row :gutter="16">
<a-col
v-bind="
styleResponsive ? { md: 12, sm: 24, xs: 24 } : { span: 12 }
"
>
<a-form-item label="商品名称" name="goodsName">
<a-input
allow-clear
placeholder="请输入商品名称"
v-model:value="form.goodsName"
/>
</a-form-item>
<a-form-item label="商品分类" name="categoryId">
<category-select
:data="categoryList"
placeholder="请选择商品分类"
v-model:value="form.categoryId"
/>
</a-form-item>
<a-form-item label="商品类型" name="goodsType">
<a-radio-group v-model:value="form.goodsType">
<a-radio :value="1">实物商品</a-radio>
<a-radio :value="0">虚拟商品</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item
label="配送方式"
name="deliveryType"
v-if="form.goodsType === 1"
>
<a-radio-group v-model:value="form.deliveryType">
<a-radio :value="0">系统默认</a-radio>
<a-radio :value="1">单独配送</a-radio>
</a-radio-group>
</a-form-item>
</a-col>
<a-col
v-bind="
styleResponsive ? { md: 12, sm: 24, xs: 24 } : { span: 12 }
"
>
<a-form-item label="商品编码" name="goodsCode">
<a-input
allow-clear
placeholder="请输入商品编码"
:disabled="isUpdate"
v-model:value="form.goodsCode"
/>
</a-form-item>
<a-form-item label="商品状态" name="status">
<a-radio-group v-model:value="form.status">
<a-radio :value="0">上架</a-radio>
<a-radio :value="1">下架</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item
label="运费模板"
name="deliveryId"
v-if="form.goodsType === 1"
>
<deliver-select v-model:value="form.deliveryId" />
</a-form-item>
</a-col>
</a-row>
<a-form-item label="商品图片" name="images">
<ele-image-upload
v-model:value="images"
:limit="14"
:drag="true"
:multiple="true"
:upload-handler="uploadHandler"
@upload="onUpload"
/>
</a-form-item>
</a-form>
</a-tab-pane>
<a-tab-pane key="2" tab="规格/库存">
<a-form
ref="formRef"
:label-col="{ md: { span: 8 }, sm: { span: 24 } }"
:wrapper-col="{ md: { span: 24 }, sm: { span: 24 } }"
layout="vertical"
:model="form"
:rules="rules"
>
<a-form-item label="规格类型" name="specType">
<a-radio-group v-model:value="form.specType">
<a-radio :value="0">单规格</a-radio>
<!-- <a-radio :value="1">多规格</a-radio>-->
</a-radio-group>
</a-form-item>
<div style="margin-bottom: 22px">
<a-divider />
</div>
<a-row :gutter="16">
<a-col
v-bind="
styleResponsive ? { md: 12, sm: 24, xs: 24 } : { span: 12 }
"
>
<a-form-item
label="商品价格"
name="goodsPriceMin"
v-if="form.specType === 0"
>
<a-input-number
:min="0.01"
:precision="2"
style="width: 200px"
v-model:value="form.goodsPriceMin"
/>
<span class="ml-10">元</span>
</a-form-item>
<a-form-item
label="划线价"
name="linePrice"
v-if="form.specType === 0"
>
<a-input-number
:min="0.01"
:precision="2"
style="width: 200px"
v-model:value="form.linePriceMin"
/>
<span class="ml-10">元</span>
</a-form-item>
</a-col>
<a-col
v-bind="
styleResponsive ? { md: 12, sm: 24, xs: 24 } : { span: 12 }
"
>
<a-form-item
label="当前库存数量"
name="stockTotal"
v-if="form.specType === 0"
>
<a-input-number
:min="0"
:precision="0"
style="width: 200px"
v-model:value="form.stockTotal"
/>
<span class="ml-10">件</span>
</a-form-item>
<a-form-item
label="商品重量"
name="goodsWeight"
v-if="form.specType === 0"
>
<a-input-number
:min="0"
:precision="0"
style="width: 200px"
v-model:value="form.goodsWeight"
/>
<span class="ml-10">千克 (Kg)</span>
</a-form-item>
</a-col>
</a-row>
<a-form-item
label="商品规格"
name="deductStockType"
v-if="form.specType === 1"
>
<!-- <MultiSpec ref="MultiSpec" />-->
</a-form-item>
<div style="margin-bottom: 22px">
<a-divider />
</div>
<a-form-item label="库存计算方式" name="deductStockType">
<a-radio-group v-model:value="form.deductStockType">
<a-radio :value="10">下单减库存</a-radio>
<a-radio :value="20">付款减库存</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item label="商品限购" name="purchaseLimit">
<a-radio-group v-model:value="form.purchaseLimit">
<a-radio :value="0">关闭</a-radio>
<a-radio :value="1">开启</a-radio>
</a-radio-group>
</a-form-item>
</a-form>
</a-tab-pane>
<a-tab-pane key="3" tab="商品详情">
<a-form
ref="formRef"
:label-col="{ md: { span: 8 }, sm: { span: 24 } }"
:wrapper-col="{ md: { span: 24 }, sm: { span: 24 } }"
layout="vertical"
:model="form"
:rules="rules"
>
<a-form-item label="" name="content">
<!-- 编辑器 -->
<tinymce-editor
v-model:value="content"
:disabled="disabled"
:init="config"
/>
</a-form-item>
</a-form>
</a-tab-pane>
<a-tab-pane key="4" tab="更多设置">
<a-form
ref="formRef"
:label-col="{ md: { span: 8 }, sm: { span: 24 } }"
:wrapper-col="{ md: { span: 24 }, sm: { span: 24 } }"
layout="vertical"
:model="form"
:rules="rules"
>
<a-form-item label="商品卖点" name="sellingPoint">
<a-input
allow-clear
placeholder="【入手皆因热爱】限时限量领券至高立减400元,限时优惠,限量抢购!!赶快下单吧!"
v-model:value="form.sellingPoint"
/>
</a-form-item>
<a-form-item label="排序号" name="sortNumber">
<a-input-number
:min="0"
:max="99999"
class="ele-fluid"
placeholder="请输入排序号(数字越小越靠前)"
v-model:value="form.sortNumber"
/>
</a-form-item>
</a-form>
</a-tab-pane>
</a-tabs>
</ele-modal>
</template>
<script lang="ts" setup>
import { ref, reactive, watch } from 'vue';
import { message } from 'ant-design-vue';
import type { Goods } from '@/api/goods/model';
import { addGoods, updateGoods } from '@/api/goods';
import { FormInstance, Rule, RuleObject } from 'ant-design-vue/es/form';
import { ItemType } from 'ele-admin-pro/es/ele-image-upload/types';
import { uploadFile } from '@/api/system/file';
import { FILE_THUMBNAIL } from '@/config/setting';
import useFormData from '@/utils/use-form-data';
import CategorySelect from './category-select.vue';
import TinymceEditor from '@/components/TinymceEditor/index.vue';
import { Category } from '@/api/goods/category/model';
import { useThemeStore } from '@/store/modules/theme';
import { storeToRefs } from 'pinia';
import DeliverSelect from './deliver-select.vue';
import { createCode } from '@/utils/common';
// import MultiSpec from './MultiSpec.vue';
// 是否是修改
const isUpdate = ref(false);
// 是否显示最大化切换按钮
const maxAble = ref(true);
const disabled = ref(false);
const props = defineProps<{
// 弹窗是否打开
visible: boolean;
// 修改回显的数据
data?: Goods | null;
categoryList?: Category[];
}>();
const emit = defineEmits<{
(e: 'done'): void;
(e: 'update:visible', visible: boolean): void;
}>();
// 是否开启响应式布局
const themeStore = useThemeStore();
const { styleResponsive } = storeToRefs(themeStore);
// 提交状态
const loading = ref(false);
// 已上传数据
const images = ref<ItemType[]>([]);
const formRef = ref<FormInstance | null>(null);
// 选项卡位置
const activeKey = ref('1');
// 编辑器内容,双向绑定
const content = ref<any>('');
/* 更新visible */
const updateVisible = (value: boolean) => {
emit('update:visible', value);
};
// 表单数据
const { form, resetFields, assignFields } = useFormData<Goods>({
goodsType: 1,
goodsName: '',
goodsCode: `G${createCode()}`,
categoryId: 0,
image: '',
files: '',
goodsPriceMin: 0.01,
linePriceMin: 0.01,
createTime: '',
comments: '',
sortNumber: 100,
status: 0,
goodsId: 0,
specType: 0,
deliveryType: 0,
content: '',
deliveryId: 0,
sellingPoint: '',
purchaseLimit: 0,
deductStockType: 10,
salesActual: 180,
goodsWeight: 0,
stockTotal: 2000
});
/* 上传事件 */
const uploadHandler = (file: File) => {
const item: ItemType = {
file,
uid: (file as any).uid,
name: file.name
};
if (!file.type.startsWith('image')) {
message.error('只能选择图片');
return;
}
if (file.size / 1024 / 1024 > 2) {
message.error('大小不能超过 2MB');
return;
}
onUpload(item);
};
// 上传文件
const onUpload = (item) => {
const { file } = item;
uploadFile(file)
.then((data) => {
images.value.push({
uid: data.id,
url: FILE_THUMBNAIL + data.path,
status: 'done'
});
message.success('上传成功');
})
.catch((e) => {
message.error(e.message);
});
};
// 表单验证规则
const rules = reactive<Record<string, Rule[]>>({
goodsName: [
{
required: true,
message: '请输入商品名称',
type: 'string',
trigger: 'blur'
}
],
categoryId: [
{
required: true,
type: 'number',
message: '请选择商品分类',
trigger: 'blur'
}
],
goodsCode: [
{
required: true,
type: 'string',
message: '请填写商品编号',
trigger: 'blur'
}
],
goodsType: [
{
required: true,
type: 'number',
message: '请选择商品类型',
trigger: 'blur'
}
],
deliveryType: [
{
required: true,
type: 'number',
message: '请选择配送方式',
trigger: 'blur'
}
],
goodsPriceMin: [
{
required: true,
type: 'number',
message: '请输入商品价格',
trigger: 'blur'
}
],
stockTotal: [
{
required: true,
type: 'number',
message: '请输入商品库存',
trigger: 'blur'
}
],
specType: [
{
required: true,
type: 'number',
message: '请选择规格类型',
trigger: 'blur'
}
],
deliveryId: [
{
required: true,
type: 'number',
message: '请选择运费模板',
trigger: 'blur'
}
],
status: [
{
required: true,
type: 'number',
message: '请选择商品状态',
trigger: 'blur'
}
],
sortNumber: [
{
required: true,
type: 'number',
message: '请输入排序号',
trigger: 'blur'
}
],
deductStockType: [
{
required: true,
type: 'number',
message: '请设置库存计算方式',
trigger: 'blur'
}
],
purchaseLimit: [
{
required: true,
type: 'number',
message: '请设置是否限购',
trigger: 'blur'
}
],
images: [
{
required: true,
type: 'string',
message: '请选择商品图片',
trigger: 'blur',
validator: async (_rule: RuleObject, value: string) => {
if (images.value.length == 0) {
return Promise.reject('请上传商品图片');
}
return Promise.resolve();
}
}
],
customerId: [
{
required: true,
type: 'number',
message: '请选择客户'
}
],
linePriceMin: [
{
required: true,
type: 'number',
message: '请输入商品划线价',
trigger: 'blur'
}
],
content: [
{
required: true,
type: 'string',
message: '请输入文章内容',
trigger: 'blur',
validator: async (_rule: RuleObject, value: string) => {
if (content.value == '') {
return Promise.reject('请输入文字内容');
}
return Promise.resolve();
}
}
],
brand: [
{
required: true,
type: 'string',
message: '请选项商品厂商',
trigger: 'blur'
}
]
});
const editorRef = ref<InstanceType<typeof TinymceEditor> | null>(null);
const config = ref({
height: 500,
// 自定义文件上传(这里使用把选择的文件转成 blob 演示)
file_picker_callback: (callback: any, _value: any, meta: any) => {
const input = document.createElement('input');
input.setAttribute('type', 'file');
// 设定文件可选类型
if (meta.filetype === 'image') {
input.setAttribute('accept', 'image/*');
} else if (meta.filetype === 'media') {
input.setAttribute('accept', 'video/*');
}
input.onchange = () => {
const file = input.files?.[0];
if (!file) {
return;
}
if (meta.filetype === 'media') {
if (!file.type.startsWith('video/')) {
editorRef.value?.alert({ content: '只能选择视频文件' });
return;
}
}
if (file.size / 1024 / 1024 > 20) {
editorRef.value?.alert({ content: '大小不能超过 20MB' });
return;
}
const reader = new FileReader();
reader.onload = (e) => {
if (e.target?.result != null) {
const blob = new Blob([e.target.result], { type: file.type });
callback(URL.createObjectURL(blob));
}
};
reader.readAsArrayBuffer(file);
};
input.click();
}
});
// const onSelect = (item) => {
// form.goodsId = item.goodsId;
// };
/* 保存编辑 */
const save = () => {
if (!formRef.value) {
return;
}
formRef.value
.validate()
.then(() => {
loading.value = true;
// 获取第一张图片作为封面图
images.value.map((d, i) => {
if (i === 0) {
form.image = d.url;
}
});
const goodsForm = {
...form,
files: JSON.stringify(images.value),
content: content.value
};
const saveOrUpdate = isUpdate.value ? updateGoods : addGoods;
saveOrUpdate(goodsForm)
.then((msg) => {
loading.value = false;
message.success(msg);
updateVisible(false);
emit('done');
})
.catch((e) => {
loading.value = false;
message.error(e.message);
});
})
.catch(() => {});
};
watch(
() => props.visible,
(visible) => {
if (visible) {
if (props.data) {
loading.value = false;
assignFields({
...props.data,
files: JSON.parse(String(props.data.files))
});
images.value = JSON.parse(String(props.data.files));
content.value = props.data.content;
isUpdate.value = true;
} else {
isUpdate.value = false;
content.value = '';
}
} else {
resetFields();
formRef.value?.clearValidate();
}
}
);
</script>
<style lang="less">
.tab-pane {
min-height: 300px;
}
.ml-10 {
margin-left: 5px;
}
</style>
@@ -0,0 +1,243 @@
<!-- 用户编辑弹窗 -->
<template>
<a-drawer
width="75%"
:visible="visible"
:confirm-loading="loading"
:maxable="maxAble"
:title="'商品详情'"
:body-style="{ paddingBottom: '8px' }"
@update:visible="updateVisible"
:footer="null"
>
<a-form
:label-col="{ md: { span: 4 }, sm: { span: 24 } }"
:wrapper-col="{ md: { span: 19 }, sm: { span: 24 } }"
>
<div class="base-form" style="margin-bottom: 20px">
<a-descriptions :title="assets.name" bordered>
<a-descriptions-item label="商品名称">
{{ assets.name }}
</a-descriptions-item>
<a-descriptions-item label="商品IP">
{{ assets.code }}
</a-descriptions-item>
<a-descriptions-item label="状态">
<a-tag v-if="assets.status === '0'" color="green">正常</a-tag>
<a-tag v-if="assets.status === '1'" color="red">过期</a-tag>
<a-tag v-if="assets.status === '2'" color="purple">不再续</a-tag>
</a-descriptions-item>
<a-descriptions-item label="宝塔面板">
<a href="#" @click="openUrl(assets)">{{ assets.panel }}</a>
</a-descriptions-item>
<a-descriptions-item label="宝塔账号">
{{ assets.panelAccount }}
</a-descriptions-item>
<a-descriptions-item label="宝塔密码">
<span v-role="['admin']">{{ assets.panelPassword }}</span>
<span v-any-role="['user', 'guest', 'superAdmin', 'developer']"
>****</span
>
</a-descriptions-item>
<a-descriptions-item label="品牌厂商">
{{ assets.brand }}
</a-descriptions-item>
<a-descriptions-item label="商品配置">
{{ assets.configuration }}
</a-descriptions-item>
<a-descriptions-item label="开发端口">
{{ assets.openPort }}
</a-descriptions-item>
<a-descriptions-item label="有效期">
<div v-if="assets.startTime && assets.endTime">
<div class="ele-text-info">{{ assets.startTime }}</div>
<div v-html="getEndTime(assets.endTime)"></div>
</div>
</a-descriptions-item>
<a-descriptions-item label="商品账号">
<span class="ele-text-danger">root</span>
</a-descriptions-item>
<a-descriptions-item label="商品密码">
<span class="ele-text-danger" v-role="['admin']">{{
assets.password
}}</span>
<span v-any-role="['user', 'guest', 'superAdmin', 'developer']"
>****</span
>
</a-descriptions-item>
<a-descriptions-item label="创建时间">
{{ timeAgo(assets.createTime) }}
</a-descriptions-item>
<a-descriptions-item label="排序">
{{ assets.sortNumber }}
</a-descriptions-item>
<a-descriptions-item label="发布人">
{{ assets.userId }}
</a-descriptions-item>
<a-descriptions-item span="1" label="备注">
{{ assets.comments }}
</a-descriptions-item>
</a-descriptions>
</div>
<a-space style="display: none">
<a-tabs v-model:activeKey="activeKey" v-role="'admin'">
<a-tab-pane key="1" tab="宝塔面板" force-render>
<a-descriptions title="宝塔面板" class="tab-pane">
<a-descriptions-item label="宝塔面板">
<a href="#" @click="openUrl(assets)">{{ assets.panel }}</a>
</a-descriptions-item>
<a-descriptions-item label="宝塔面板账号">
{{ assets.panelAccount }}
</a-descriptions-item>
<a-descriptions-item label="宝塔面板密码">
{{ assets.panelPassword }}
</a-descriptions-item>
<a-descriptions-item label="开发端口">
{{ assets.openPort }}
</a-descriptions-item>
</a-descriptions>
</a-tab-pane>
<a-tab-pane key="2" tab="账号密码" force-render>
<a-descriptions title="商品账号密码" class="tab-pane">
<a-descriptions-item label="商品账号">
root
</a-descriptions-item>
<a-descriptions-item label="商品密码">
{{ assets.password }}
</a-descriptions-item>
</a-descriptions>
<a-descriptions title="账号密码" class="tab-pane">
<a-descriptions-item label="登录账号">
{{ assets.brandAccount }}
</a-descriptions-item>
<a-descriptions-item label="登录密码">
{{ assets.brandPassword }}
</a-descriptions-item>
</a-descriptions>
</a-tab-pane>
<a-tab-pane key="3" tab="财务信息">
<a-descriptions title="财务信息" class="tab-pane">
<a-descriptions-item label="合同金额">
<span class="ele-text-warning">
¥{{ assets.financeAmount }}
</span>
</a-descriptions-item>
<a-descriptions-item label="购买年限">
{{ assets.financeYears }}年
</a-descriptions-item>
<a-descriptions-item label="每年续费金额">
<span class="ele-text-warning">
¥{{ assets.financeRenew }}
</span>
</a-descriptions-item>
<a-descriptions-item label="客户名称">
{{ assets.financeCustomerName }}
</a-descriptions-item>
<a-descriptions-item label="客户联系人">
{{ assets.financeCustomerContact }}
</a-descriptions-item>
<a-descriptions-item label="联系方式">
{{ assets.financeCustomerPhone }}
</a-descriptions-item>
</a-descriptions>
</a-tab-pane>
</a-tabs>
</a-space>
</a-form>
</a-drawer>
</template>
<script lang="ts" setup>
import { getEndTime } from '@/utils/common';
import { ref, reactive, watch } from 'vue';
import { Form } from 'ant-design-vue';
import { assignObject, timeAgo } from 'ele-admin-pro';
import type { Assets } from '@/api/oa/assets/model';
const useForm = Form.useForm;
const props = defineProps<{
// 弹窗是否打开
visible: boolean;
// 修改回显的数据
data?: Assets | null;
}>();
const emit = defineEmits<{
(e: 'done'): void;
(e: 'update:visible', visible: boolean): void;
}>();
// 用户信息
const assets = reactive<Assets>({
code: '',
createTime: '',
startTime: '',
endTime: '',
name: '',
brand: undefined,
account: '',
password: '',
configuration: '',
panelAccount: '',
panelPassword: '',
panel: '',
financeAmount: 0,
financeYears: 0,
financeRenew: 0,
financeCustomerName: '',
financeCustomerContact: '',
financeCustomerPhone: '',
brandAccount: '',
brandPassword: '',
openPort: '',
comments: '',
root: 'root',
userId: 0,
sortNumber: undefined,
status: undefined,
type: '',
assetsId: 0
});
// 请求状态
const loading = ref(true);
// 是否显示最大化切换按钮
const maxAble = ref(true);
// 选项卡位置
const activeKey = ref('1');
const { resetFields } = useForm(assets);
/* 更新visible */
const updateVisible = (value: boolean) => {
emit('update:visible', value);
};
/* 打开外部链接 */
const openUrl = (record) => {
window.open(record.panel);
};
watch(
() => props.visible,
(visible) => {
if (visible) {
if (props.data) {
loading.value = false;
assignObject(assets, props.data);
}
} else {
resetFields();
}
}
);
</script>
<style lang="less">
.tab-pane {
min-height: 100px;
}
.important {
font-weight: bold;
}
</style>
@@ -0,0 +1,65 @@
<!-- 搜索表单 -->
<template>
<a-form
:label-col="{ md: { span: 6 }, sm: { span: 24 } }"
:wrapper-col="{ md: { span: 18 }, sm: { span: 24 } }"
>
<a-row>
<a-col :lg="6" :md="12" :sm="24" :xs="24">
<a-form-item label="服务器名称">
<a-input
v-model:value.trim="where.name"
placeholder="请输入服务器名称"
allow-clear
@pressEnter="search"
/>
</a-form-item>
</a-col>
<a-col :lg="6" :md="12" :sm="24" :xs="24">
<a-form-item label="IP地址">
<a-input
v-model:value.trim="where.code"
placeholder="请输入服务器IP"
allow-clear
@pressEnter="search"
/>
</a-form-item>
</a-col>
<a-col :lg="6" :md="12" :sm="24" :xs="24">
<a-form-item class="ele-text-right" :wrapper-col="{ span: 24 }">
<a-space>
<a-button type="primary" @click="search">查询</a-button>
<a-button @click="reset">重置</a-button>
</a-space>
</a-form-item>
</a-col>
</a-row>
</a-form>
</template>
<script lang="ts" setup>
import useSearch from '@/utils/use-search';
import type { AssetsParam } from '@/api/oa/assets/model';
const emit = defineEmits<{
(e: 'search', where?: AssetsParam): void;
}>();
// 表单数据
const { where, resetFields } = useSearch<AssetsParam>({
name: '',
code: '',
isExpire: ''
});
/* 搜索 */
const search = () => {
emit('search', where);
};
/* 重置 */
const reset = () => {
resetFields();
search();
};
</script>
@@ -0,0 +1,289 @@
<!-- 用户编辑弹窗 -->
<template>
<ele-modal
:width="680"
:visible="visible"
:confirm-loading="loading"
:maskClosable="false"
:maxable="maxable"
:title="isUpdate ? '编辑订单' : '添加订单'"
:body-style="{ paddingBottom: '8px' }"
@update:visible="updateVisible"
@ok="save"
>
<a-space>
<a-form
:label-col="{ md: { span: 7 }, sm: { span: 4 }, xs: { span: 24 } }"
:wrapper-col="{ md: { span: 17 }, sm: { span: 20 }, xs: { span: 24 } }"
>
<a-row :gutter="16">
<a-col :md="12" :sm="24" :xs="24">
<a-form-item label="订单名称" v-bind="validateInfos.customerName">
<a-input
allow-clear
:maxlength="30"
placeholder="请输入订单名称"
v-model:value="form.customerName"
@blur="
validate('customerName', { trigger: 'blur' }).catch(() => {})
"
/>
</a-form-item>
<a-form-item label="订单标识" v-bind="validateInfos.customerCode">
<a-input
allow-clear
:maxlength="20"
placeholder="请输入用户账号"
v-model:value="form.customerCode"
/>
</a-form-item>
<a-form-item label="跟进状态" v-bind="validateInfos.progress">
<progress-select
v-model:value="form.progress"
@blur="
validate('progress', { trigger: 'blur' }).catch(() => {})
"
/>
</a-form-item>
<a-form-item label="订单类型" v-bind="validateInfos.customerType">
<type-select
v-model:value="form.customerType"
@blur="
validate('customerType', { trigger: 'blur' }).catch(() => {})
"
/>
</a-form-item>
<a-form-item label="订单来源" v-bind="validateInfos.customerSource">
<source-select
v-model:value="form.customerSource"
@blur="
validate('customerSource', { trigger: 'blur' }).catch(() => {})
"
/>
</a-form-item>
<a-form-item label="头像" v-bind="validateInfos.customerAvatar">
<ele-image-upload
v-model:value="images"
:item-style="{ width: '90px', height: '90px' }"
:limit="1"
@upload="onUpload"
/>
</a-form-item>
</a-col>
<a-col :md="12" :sm="24" :xs="24">
<a-form-item label="联系人" v-bind="validateInfos.customerContacts">
<a-input
allow-clear
:maxlength="20"
placeholder="请填写联系人"
v-model:value="form.customerContacts"
/>
</a-form-item>
<a-form-item label="手机号码" v-bind="validateInfos.customerMobile">
<a-input
allow-clear
:maxlength="20"
placeholder="请填写联系人手机号码"
v-model:value="form.customerMobile"
/>
</a-form-item>
<a-form-item
label="联系地址"
v-bind="validateInfos.customerAddress"
>
<a-input
allow-clear
placeholder="请填写联系地址"
v-model:value="form.customerAddress"
/>
</a-form-item>
<a-form-item label="公司座机" v-bind="validateInfos.customerPhone">
<a-input
allow-clear
:maxlength="20"
placeholder="请填写公司座机电话"
v-model:value="form.customerPhone"
/>
</a-form-item>
<a-form-item label="排序" v-bind="validateInfos.sortNumber">
<a-input
allow-clear
:maxlength="20"
placeholder="排序"
v-model:value="form.sortNumber"
/>
</a-form-item>
<a-form-item label="备注" v-bind="validateInfos.comments">
<a-textarea
:rows="4"
:maxlength="200"
placeholder="请输入备注"
v-model:value="form.comments"
/>
</a-form-item>
</a-col>
</a-row>
</a-form>
</a-space>
</ele-modal>
</template>
<script lang="ts" setup>
import {ref, reactive, watch, computed} from 'vue';
import { Form, message } from 'ant-design-vue';
import { assignObject } from 'ele-admin-pro';
import TypeSelect from './customer-edit/type-select.vue';
import ProgressSelect from './customer-edit/progress-select.vue';
import SourceSelect from './customer-edit/source-select.vue';
import { addCustomer, updateCustomer } from '@/api/oa/customer';
import type { Customer } from '@/api/oa/customer/model';
import { createCode } from '@/utils/common';
import { uploadFile } from '@/api/system/file';
import type { ItemType } from 'ele-admin-pro/es/ele-image-upload/types';
import { FILE_SERVER } from '@/config/setting';
import { useUserStore } from '@/store/modules/user';
const userStore = useUserStore();
// 当前用户信息
const loginUser = computed(() => userStore.info ?? {});
// 是否是修改
const isUpdate = ref(false);
const useForm = Form.useForm;
const props = defineProps<{
// 弹窗是否打开
visible: boolean;
// 修改回显的数据
data?: Customer | null;
}>();
const emit = defineEmits<{
(e: 'done'): void;
(e: 'update:visible', visible: boolean): void;
}>();
// 提交状态
const loading = ref(false);
// 是否显示最大化切换按钮
const maxable = ref(true);
// 用户信息
const form = reactive<Customer>({
customerCode: '',
customerName: '',
customerType: undefined,
progress: undefined,
customerMobile: '',
customerAvatar: '',
customerPhone: '',
customerContacts: '',
customerAddress: '',
comments: '',
status: '0',
sortNumber: 100,
customerId: 0,
userId: '',
});
// 已上传数据, 可赋初始值用于回显
const images = ref(<any>[]);
/* 更新visible */
const updateVisible = (value: boolean) => {
emit('update:visible', value);
};
// 表单验证规则
const rules = reactive({
customerName: [
{
required: true,
type: 'string',
message: '请输入订单名称',
trigger: 'blur'
}
],
customerCode: [
{
required: true,
type: 'string',
message: '请输入合法的IP地址',
trigger: 'blur'
}
],
progress: [
{
required: true,
type: 'string',
message: '请选择跟进状态',
trigger: 'blur'
}
]
});
const { resetFields, validate, validateInfos } = useForm(form, rules);
/* 保存编辑 */
const save = () => {
validate()
.then(() => {
loading.value = true;
// 去除空格
form.customerName = form.customerName?.replace(/\s*/g, '');
if(isUpdate.value == false) {
form.userId = loginUser.value.userId;
}
const data = {
...form
};
// 转字符串
const saveOrUpdate = isUpdate.value ? updateCustomer : addCustomer;
saveOrUpdate(data)
.then((msg) => {
loading.value = false;
message.success(msg);
updateVisible(false);
emit('done');
})
.catch((e) => {
loading.value = false;
message.error(e.message);
});
})
.catch(() => {});
};
const onUpload = (d: ItemType) => {
uploadFile(<File>d.file)
.then((result) => {
form.customerAvatar = result.path;
message.success('上传成功');
})
.catch((e) => {
message.error(e.message);
});
};
watch(
() => props.visible,
(visible) => {
if (visible) {
if (props.data) {
loading.value = false;
// 头像赋值
images.value = [];
if(props.data.customerAvatar){
images.value.push({ uid:1, url: FILE_SERVER + props.data.customerAvatar, status: '' });
}
assignObject(form, props.data);
isUpdate.value = true;
} else {
form.customerCode = createCode();
isUpdate.value = false;
}
} else {
resetFields();
}
}
);
</script>
<style lang="less"></style>
@@ -0,0 +1,155 @@
<!-- 用户编辑弹窗 -->
<template>
<ele-modal
width="75%"
:visible="visible"
:confirm-loading="loading"
:title="'订单详情'"
:maxable="true"
:body-style="{ paddingBottom: '8px' }"
@update:visible="updateVisible"
:footer="null"
>
<a-form
:label-col="{ md: { span: 4 }, sm: { span: 24 } }"
:wrapper-col="{ md: { span: 19 }, sm: { span: 24 } }"
>
<div class="base-form" style="margin-bottom: 20px">
<div class="card-head">
<a-avatar
:size="50"
style="margin-right: 10px"
:src="
customer.customerAvatar
? FILE_SERVER + customer.customerAvatar
: ''
"
/>
<h2>{{ customer.customerName }}</h2>
</div>
<a-descriptions bordered>
<a-descriptions-item label="订单名称">
{{ customer.customerName }}
</a-descriptions-item>
<a-descriptions-item label="订单标识">
{{ customer.customerCode }}
</a-descriptions-item>
<a-descriptions-item label="跟进状态">
<div color="blue" v-for="(d, index) in progress" :key="index">
<span v-if="d.key == customer.progress">{{ d.name }}</span>
</div>
</a-descriptions-item>
<a-descriptions-item label="座机电话">
{{ customer.customerPhone }}
</a-descriptions-item>
<a-descriptions-item label="联系人">
{{ customer.customerContacts }}
</a-descriptions-item>
<a-descriptions-item label="订单类型">
<div color="blue" v-for="(d, index) in customerType" :key="index">
<span v-if="d.key == customer.customerType">{{ d.name }}</span>
</div>
</a-descriptions-item>
<a-descriptions-item label="联系地址">
{{ customer.customerAddress }}
</a-descriptions-item>
<a-descriptions-item label="联系电话">
{{ customer.customerMobile }}
</a-descriptions-item>
<a-descriptions-item label="备注">
{{ customer.comments }}
</a-descriptions-item>
</a-descriptions>
<!-- <a-descriptions-->
<!-- title="其他信息"-->
<!-- :column="1"-->
<!-- bordered-->
<!-- style="margin-top: 30px"-->
<!-- >-->
<!-- <a-descriptions-item label="相关项目">-->
<!-- {{ customer.comments }}-->
<!-- </a-descriptions-item>-->
<!-- </a-descriptions>-->
</div>
</a-form>
</ele-modal>
</template>
<script lang="ts" setup>
import { ref, reactive, watch } from 'vue';
import { Form } from 'ant-design-vue';
import { assignObject } from 'ele-admin-pro';
import type { Customer } from '@/api/oa/customer/model';
import { FILE_SERVER } from '@/config/setting';
import { getDictionaryOptions } from '@/utils/common';
const useForm = Form.useForm;
const props = defineProps<{
// 弹窗是否打开
visible: boolean;
// 修改回显的数据
data?: Customer | null;
}>();
const emit = defineEmits<{
(e: 'done'): void;
(e: 'update:visible', visible: boolean): void;
}>();
// 用户信息
const customer = reactive<Customer>({
customerCode: '',
customerName: '',
customerType: undefined,
customerMobile: '',
customerAvatar: '',
customerPhone: '',
customerContacts: '',
customerAddress: '',
comments: '',
progress: '',
status: '0',
sortNumber: 100,
customerId: 0
});
// 请求状态
const loading = ref(true);
const { resetFields } = useForm(customer);
/* 更新visible */
const updateVisible = (value: boolean) => {
emit('update:visible', value);
};
/* 获取字典数据 */
const customerType = getDictionaryOptions('customerType');
const progress = getDictionaryOptions('customerFollowStatus');
watch(
() => props.visible,
(visible) => {
if (visible) {
if (props.data) {
loading.value = false;
assignObject(customer, props.data);
}
} else {
resetFields();
}
}
);
</script>
<style lang="less">
.tab-pane {
min-height: 100px;
}
.card-head {
display: flex;
height: 40px;
align-items: center;
margin-bottom: 30px;
}
</style>
@@ -0,0 +1,46 @@
<!-- 选择下拉框 -->
<template>
<a-select
mode="multiple"
show-search
optionFilterProp="label"
:options="data"
allow-clear
:value="value"
:placeholder="placeholder"
@update:value="updateValue"
@blur="onBlur"
/>
</template>
<script lang="ts" setup>
import { getDictionaryOptions } from '@/utils/common';
const emit = defineEmits<{
(e: 'update:value', value: string): void;
(e: 'blur'): void;
}>();
withDefaults(
defineProps<{
value?: string;
placeholder?: string;
}>(),
{
placeholder: '请选择已开发的端口'
}
);
// 字典数据
const data = getDictionaryOptions('serverOpenPort');
/* 更新选中数据 */
const updateValue = (value: string) => {
emit('update:value', value);
};
/* 失去焦点 */
const onBlur = () => {
emit('blur');
};
</script>
@@ -0,0 +1,50 @@
<!-- 选择下拉框 -->
<template>
<a-space>
<a-button type="dashed" @click="reset">全部</a-button>
<a-button
v-for="(item, index) in data"
:key="index"
type="dashed"
v-model:value.trim="where.progress"
@click="search(item.value)"
>{{ item.label }}</a-button
>
</a-space>
</template>
<script lang="ts" setup>
import { CustomerParam } from '@/api/oa/customer/model';
import useSearch from '@/utils/use-search';
import { getDictionaryOptions } from '@/utils/common';
const emit = defineEmits<{
(e: 'search', where?: CustomerParam): void;
}>();
// 字典数据
const data = getDictionaryOptions('customerFollowStatus');
// 表单数据
const { where, resetFields } = useSearch<CustomerParam>({
progress: '',
customerType: '',
customerSource: '',
betweenTime: '',
userId: ''
});
/* 搜索 */
const search = (val) => {
where.progress = val == 'all' ? null : val;
emit('search', where);
};
/* 重置 */
const reset = () => {
resetFields();
search('all');
};
</script>
<style lang="less"></style>
@@ -0,0 +1,50 @@
<!-- 选择下拉框 -->
<template>
<a-space>
<a-button type="dashed" @click="reset">全部</a-button>
<a-button
v-for="(item, index) in data"
:key="index"
type="dashed"
v-model:value.trim="where.customerSource"
@click="search(item.value)"
>{{ item.label }}</a-button
>
</a-space>
</template>
<script lang="ts" setup>
import { CustomerParam } from '@/api/oa/customer/model';
import useSearch from '@/utils/use-search';
import { getDictionaryOptions } from '@/utils/common';
const emit = defineEmits<{
(e: 'search', where?: CustomerParam): void;
}>();
// 字典数据
const data = getDictionaryOptions('customerSource');
// 表单数据
const { where, resetFields } = useSearch<CustomerParam>({
progress: '',
customerType: '',
customerSource: '',
betweenTime: '',
userId: ''
});
/* 搜索 */
const search = (val) => {
where.customerSource = val == 'all' ? null : val;
emit('search', where);
};
/* 重置 */
const reset = () => {
resetFields();
search('all');
};
</script>
<style lang="less"></style>
@@ -0,0 +1,45 @@
<!-- 选择下拉框 -->
<template>
<a-select
show-search
optionFilterProp="label"
:options="data"
allow-clear
:value="value"
:placeholder="placeholder"
@update:value="updateValue"
@blur="onBlur"
/>
</template>
<script lang="ts" setup>
import { getDictionaryOptions } from '@/utils/common';
const emit = defineEmits<{
(e: 'update:value', value: string): void;
(e: 'blur'): void;
}>();
withDefaults(
defineProps<{
// value?: string;
placeholder?: string;
}>(),
{
placeholder: '请选择状态'
}
);
// 字典数据
const data = getDictionaryOptions('status');
/* 更新选中数据 */
const updateValue = (value: string) => {
emit('update:value', value);
};
/* 失去焦点 */
const onBlur = () => {
emit('blur');
};
</script>
@@ -0,0 +1,48 @@
<!-- 选择下拉框 -->
<template>
<a-space>
<a-button type="dashed" @click="reset">全部</a-button>
<a-button
v-for="(item, index) in data"
:key="index"
type="dashed"
v-model:value.trim="where.betweenTime"
@click="search(item.value)"
>{{ item.label }}</a-button
>
</a-space>
</template>
<script lang="ts" setup>
import { CustomerParam } from '@/api/oa/customer/model';
import useSearch from '@/utils/use-search';
import { getDictionaryOptions } from '@/utils/common';
const emit = defineEmits<{
(e: 'search', where?: CustomerParam): void;
}>();
// 字典数据
const data = getDictionaryOptions('timeFilter');
// 表单数据
const { where, resetFields } = useSearch<CustomerParam>({
progress: '',
customerType: '',
customerSource: '',
betweenTime: '',
userId: ''
});
/* 搜索 */
const search = (val) => {
where.betweenTime = val == 'all' ? null : val;
emit('search', where);
};
/* 重置 */
const reset = () => {
resetFields();
search('all');
};
</script>
@@ -0,0 +1,53 @@
<!-- 选择下拉框 -->
<template>
<a-space>
<a-button type="dashed" @click="reset">全部</a-button>
<a-button
v-for="(item, index) in data"
:key="index"
type="dashed"
v-model:value.trim="where.customerType"
@click="search(item.value)"
>{{ item.label }}</a-button
>
</a-space>
</template>
<script lang="ts" setup>
import { CustomerParam } from '@/api/oa/customer/model';
import useSearch from '@/utils/use-search';
import { getDictionaryOptions } from '@/utils/common';
const emit = defineEmits<{
(e: 'search', where?: CustomerParam): void;
}>();
// 字典数据
const data = getDictionaryOptions('customerType');
// 表单数据
const { where, resetFields } = useSearch<CustomerParam>({
progress: '',
customerType: '',
customerSource: '',
betweenTime: '',
userId: ''
});
/* 搜索 */
const search = (val) => {
where.customerType = val == 'all' ? null : val;
emit('search', where);
console.log(where);
};
/* 重置 */
const reset = () => {
resetFields();
search('all');
};
// reset();
</script>
<style lang="less"></style>
@@ -0,0 +1,78 @@
<!-- 选择下拉框 -->
<template>
<a-select
show-search
optionFilterProp="label"
:options="data"
allow-clear
:placeholder="placeholder"
@change="onChange"
style="width: 300px"
/>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { message } from 'ant-design-vue';
import { listUsers } from '@/api/system/user';
import type { SelectProps } from 'ant-design-vue';
import { UserParam } from '@/api/system/user/model';
import { CustomerParam } from '@/api/oa/customer/model';
const emit = defineEmits<{
(e: 'search', where?: CustomerParam): void;
(e: 'update:value', value: string): void;
(e: 'blur'): void;
}>();
withDefaults(
defineProps<{
// value?: string;
placeholder?: string;
}>(),
{
placeholder: '请选择客户负责人'
}
);
// 字典数据
const data = ref<SelectProps['options']>([]);
/* 更新选中数据 */
// const updateValue = (value: string) => {
// emit('update:value', value);
// };
// 默认搜索条件
const where = ref<UserParam>({});
const search = () => {
/* 获取用户列 */
listUsers({ ...where?.value })
.then((result) => {
data.value = result?.map((d) => {
return {
value: d.userId,
label: d.nickname
};
});
})
.catch((e) => {
message.error(e.message);
});
};
const onSearch = (userId) => {
};
search();
/* 失去焦点 */
const onBlur = () => {
};
// 搜素
const onChange = (userId) => {
emit('search', { userId });
};
</script>
+140
View File
@@ -0,0 +1,140 @@
<!-- 搜索表单 -->
<template>
<a-space>
<a-button
type="primary"
class="ele-btn-icon"
v-permission="'shop:goods:save'"
@click="add"
>
<template #icon>
<PlusOutlined />
</template>
<span>新建</span>
</a-button>
<a-button
danger
type="primary"
class="ele-btn-icon"
:v-role="`dev`"
@click="removeBatch"
v-if="props.selection.length > 0"
>
<template #icon>
<DeleteOutlined />
</template>
<span>批量删除</span>
</a-button>
<a-radio-group v-model:value="listType" @change="handleTabs">
<a-radio-button value="all">全部</a-radio-button>
<a-radio-button value="onSale">出售中</a-radio-button>
<a-radio-button value="offSale">已下架</a-radio-button>
<a-radio-button value="soldOut">已售罄</a-radio-button>
</a-radio-group>
<a-input-search
allow-clear
v-model:value="searchText"
placeholder="请输入搜索关键词"
@search="search"
@pressEnter="search"
>
<template #addonBefore>
<a-select v-model:value="type" style="width: 120px; margin: -5px -12px">
<a-select-option value="goodsName">商品名称</a-select-option>
<a-select-option value="goodsCode">商品编号</a-select-option>
</a-select>
</template>
</a-input-search>
<!-- <CustomerSelect-->
<!-- v-model:value="where.goodsId"-->
<!-- :placeholder="`按商品筛选`"-->
<!-- @select="onSelect"-->
<!-- @clear="onClear"-->
<!-- />-->
</a-space>
</template>
<script lang="ts" setup>
import { PlusOutlined, DeleteOutlined } from '@ant-design/icons-vue';
import useSearch from '@/utils/use-search';
import type { GoodsParam } from '@/api/goods/model';
// import CustomerSelect from '@/components/CustomerSelect/index.vue';
import { ref, watch } from 'vue';
const emit = defineEmits<{
(e: 'search', where?: GoodsParam): void;
(e: 'add'): void;
(e: 'remove'): void;
}>();
const props = defineProps<{
// 勾选的项目
selection?: [];
}>();
// 表单数据
const { where, resetFields } = useSearch<GoodsParam>({
goodsId: undefined,
goodsName: '',
goodsCode: ''
});
// 下来选项
const type = ref('goodsName');
// tabType
const listType = ref('all');
// 搜索内容
const searchText = ref('');
/* 搜索 */
const search = () => {
if (type.value == 'goodsName') {
where.goodsName = searchText.value;
where.goodsCode = undefined;
}
if (type.value == 'goodsCode') {
where.goodsCode = searchText.value;
where.goodsName = undefined;
}
emit('search', where);
};
const handleTabs = (e) => {
listType.value = e.target.value;
if (listType.value == 'all') {
resetFields();
}
if (listType.value == 'onSale') {
where.status = 0;
}
if (listType.value == 'offSale') {
where.status = 1;
}
if (listType.value == 'soldOut') {
where.stockTotal = 0;
}
emit('search', where);
};
// 新增
const add = () => {
emit('add');
};
// 批量删除
const removeBatch = () => {
emit('remove');
};
/* 重置 */
// const reset = () => {
// resetFields();
// search();
// };
// 监听字典id变化
watch(
() => props.selection,
() => {}
);
</script>
@@ -0,0 +1,45 @@
<!-- 角色选择下拉框 -->
<template>
<a-select
show-search
optionFilterProp="label"
:options="data"
allow-clear
:value="value"
:placeholder="placeholder"
@update:value="updateValue"
@blur="onBlur"
/>
</template>
<script lang="ts" setup>
import { getDictionaryOptions } from '@/utils/common';
const emit = defineEmits<{
(e: 'update:value', value: string): void;
(e: 'blur'): void;
}>();
withDefaults(
defineProps<{
value?: string;
placeholder?: string;
}>(),
{
placeholder: '请选择年限'
}
);
// 字典数据
const data = getDictionaryOptions('serverYears');
/* 更新选中数据 */
const updateValue = (value: string) => {
emit('update:value', value);
};
/* 失去焦点 */
const onBlur = () => {
emit('blur');
};
</script>