feat: update
|
@ -1,47 +0,0 @@
|
|||
import request from "@/utils/request";
|
||||
|
||||
// 生产计划
|
||||
export function productionOrderList(params) {
|
||||
return request({ url: `/production_orders/`, method: "get", params });
|
||||
}
|
||||
|
||||
export function productionOrderDetail(params) {
|
||||
return request({ url: `/production_orders/${params.id}/`, method: "get", params });
|
||||
}
|
||||
|
||||
export function productionOrderCreate(data) {
|
||||
return request({ url: `/production_orders/`, method: "post", data });
|
||||
}
|
||||
|
||||
export function productionOrderUpdate(data) {
|
||||
return request({ url: `/production_orders/${data.id}/`, method: "put", data });
|
||||
}
|
||||
|
||||
export function productionOrderDelete(data) {
|
||||
return request({ url: `/production_orders/${data.id}/`, method: "delete", data });
|
||||
}
|
||||
|
||||
export function productionOrderNumber(params) {
|
||||
return request({ url: `/production_orders/number/`, method: "get", params });
|
||||
}
|
||||
|
||||
export function productionOrderIssue(data) {
|
||||
return request({ url: `/production_orders/${data.id}/issue/`, method: "post", data });
|
||||
}
|
||||
|
||||
export function productionOrderClose(data) {
|
||||
return request({ url: `/production_orders/${data.id}/close/`, method: "post", data });
|
||||
}
|
||||
|
||||
// 生产记录
|
||||
export function productionRecordList(params) {
|
||||
return request({ url: `/production_records/`, method: "get", params });
|
||||
}
|
||||
|
||||
export function productionRecordDetail(params) {
|
||||
return request({ url: `/production_records/${params.id}/`, method: "get", params });
|
||||
}
|
||||
|
||||
export function productionRecordCreate(data) {
|
||||
return request({ url: `/production_records/`, method: "post", data });
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
export default {
|
||||
path: '/production',
|
||||
name: 'production',
|
||||
component: () => import('@/layouts/BaseLayout'),
|
||||
redirect: '/production/plan',
|
||||
children: [
|
||||
{
|
||||
path: 'plan',
|
||||
meta: { title: '生产计划' },
|
||||
component: () => import('@/views/production/productionPlan/index'),
|
||||
},
|
||||
{
|
||||
path: 'detial',
|
||||
meta: { title: '生产计划详情' },
|
||||
component: () => import('@/views/production/productionDetial/index'),
|
||||
},
|
||||
{
|
||||
path: 'task',
|
||||
meta: { title: '生产任务' },
|
||||
component: () => import('@/views/production/productionTask/index'),
|
||||
},
|
||||
{
|
||||
path: 'record',
|
||||
meta: { title: '生产记录' },
|
||||
component: () => import('@/views/production/productionRecord/index'),
|
||||
},
|
||||
],
|
||||
}
|
|
@ -1,100 +0,0 @@
|
|||
<template>
|
||||
<div>
|
||||
<a-card title="生产计划详情">
|
||||
<a-button slot="extra" type="primary" style="margin-right: 8px;" ghost v-print="'#printContent'">
|
||||
<a-icon type="printer" />打印</a-button
|
||||
>
|
||||
<a-button
|
||||
slot="extra"
|
||||
type="primary"
|
||||
ghost
|
||||
@click="
|
||||
() => {
|
||||
this.$router.go(-1);
|
||||
}
|
||||
"
|
||||
>
|
||||
<a-icon type="left" />返回</a-button
|
||||
>
|
||||
<section id="printContent">
|
||||
<a-spin :spinning="loading">
|
||||
<img id="barcode" style="float: right" />
|
||||
<a-descriptions bordered>
|
||||
<a-descriptions-item label="生产计划单号">
|
||||
{{ item.number }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="销售单号">
|
||||
{{ item.sales_order_number }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="状态">
|
||||
{{ item.status_display }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="产品编号">
|
||||
{{ item.goods_number }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="产品名称">
|
||||
{{ item.goods_name }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="计划数量">
|
||||
{{ item.total_quantity }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="完成数量">
|
||||
{{ item.quantity_produced }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="计划开始时间">
|
||||
{{ item.start_time }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="计划结束时间">
|
||||
{{ item.end_time }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="创建时间">
|
||||
{{ item.create_time }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="创建人">
|
||||
{{ item.creator_name }}
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-spin>
|
||||
</section>
|
||||
</a-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { productionOrderDetail } from "@/api/production";
|
||||
import JsBarcode from "jsbarcode";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
item: {},
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
getJsBarcode(number) {
|
||||
JsBarcode("#barcode", number, {
|
||||
lineColor: "#000",
|
||||
width: 2,
|
||||
height: 40,
|
||||
displayValue: true,
|
||||
});
|
||||
},
|
||||
initData() {
|
||||
this.loading = true;
|
||||
productionOrderDetail({ id: this.$route.query.id })
|
||||
.then((data) => {
|
||||
this.item = data;
|
||||
this.getJsBarcode(data.number);
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.initData();
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style></style>
|
|
@ -1,129 +0,0 @@
|
|||
<template>
|
||||
<div>
|
||||
<a-modal v-model="visible" :confirmLoading="loading" :maskClosable="false" @cancel="cancel" @ok="confirm">
|
||||
<div slot="title">{{ form.id ? "编辑生产计划" : "新增生产计划" }}</div>
|
||||
<div>
|
||||
<a-form-model ref="form" :model="dataForm" :rules="rules" :label-col="{ span: 6 }" :wrapper-col="{ span: 16 }">
|
||||
<a-form-model-item prop="number" label="生产单号">
|
||||
<a-input v-model="dataForm.number" />
|
||||
</a-form-model-item>
|
||||
<a-form-model-item prop="is_related" label="类型">
|
||||
<a-radio-group v-model="dataForm.is_related" button-style="solid" @change="switchType">
|
||||
<a-radio-button :value="false">按库存生产</a-radio-button>
|
||||
<a-radio-button :value="true">按订单生产</a-radio-button>
|
||||
</a-radio-group>
|
||||
</a-form-model-item>
|
||||
<a-form-model-item v-if="!dataForm.is_related" prop="goods" label="产品">
|
||||
<goods-select v-model="dataForm.goods" :defaultItem="{ ...dataForm }" />
|
||||
</a-form-model-item>
|
||||
<a-form-model-item v-if="dataForm.is_related" prop="sales_order" label="销售单">
|
||||
<sales-order-select
|
||||
v-model="dataForm.sales_order"
|
||||
:defaultItem="{ ...dataForm }"
|
||||
@select="
|
||||
(item) => {
|
||||
dataForm.sales_goods_items = item.sales_goods_items;
|
||||
dataForm.goods = undefined;
|
||||
}
|
||||
"
|
||||
/>
|
||||
</a-form-model-item>
|
||||
<a-form-model-item v-if="dataForm.is_related" prop="goods" label="产品">
|
||||
<a-select v-model="dataForm.goods" style="width: 100%;">
|
||||
<a-select-option v-for="item in dataForm.sales_goods_items" :key="item.goods" :value="item.goods">
|
||||
{{ item.goods_name }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-model-item>
|
||||
<a-form-model-item prop="total_quantity" label="计划数量">
|
||||
<a-input-number v-model="dataForm.total_quantity" style="width: 100%;" />
|
||||
</a-form-model-item>
|
||||
<a-form-model-item prop="start_time" label="开始时间">
|
||||
<a-date-picker
|
||||
v-model="dataForm.start_time"
|
||||
placeholder="请选择时间"
|
||||
valueFormat="YYYY-MM-DD HH:mm:ss"
|
||||
show-time
|
||||
style="width: 100%;"
|
||||
/>
|
||||
</a-form-model-item>
|
||||
<a-form-model-item prop="end_time" label="结束时间">
|
||||
<a-date-picker
|
||||
v-model="dataForm.end_time"
|
||||
placeholder="请选择时间"
|
||||
valueFormat="YYYY-MM-DD HH:mm:ss"
|
||||
show-time
|
||||
style="width: 100%;"
|
||||
/>
|
||||
</a-form-model-item>
|
||||
</a-form-model>
|
||||
</div>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { productionOrderCreate, productionOrderUpdate, productionOrderNumber } from "@/api/production";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GoodsSelect: () => import("@/components/GoodsSelect/index"),
|
||||
SalesOrderSelect: () => import("@/components/SalesOrderSelect/index"),
|
||||
},
|
||||
props: ["visible", "form"],
|
||||
model: { prop: "visible", event: "cancel" },
|
||||
data() {
|
||||
return {
|
||||
rules: {
|
||||
number: [{ required: true, message: "请输入编号", trigger: "change" }],
|
||||
goods: [{ required: true, message: "请选择生产产品", trigger: "change" }],
|
||||
total_quantity: [{ required: true, message: "请输入计划数量", trigger: "change" }],
|
||||
},
|
||||
loading: false,
|
||||
dataForm: {},
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
confirm() {
|
||||
this.$refs.form.validate((valid) => {
|
||||
if (valid) {
|
||||
this.loading = true;
|
||||
let func = this.dataForm.id ? productionOrderUpdate : productionOrderCreate;
|
||||
func(this.dataForm)
|
||||
.then((data) => {
|
||||
this.$message.success(this.form.id ? "修改成功" : "新增成功");
|
||||
this.$emit(this.form.id ? "update" : "create", data);
|
||||
this.cancel();
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
cancel() {
|
||||
this.$emit("cancel", false);
|
||||
this.$refs.form.resetFields();
|
||||
},
|
||||
switchType() {
|
||||
this.dataForm = { ...this.dataForm, sales_order: undefined, goods: undefined };
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
visible(value) {
|
||||
if (value) {
|
||||
if (this.form.id) {
|
||||
this.dataForm = { ...this.form };
|
||||
} else {
|
||||
this.dataForm = { is_related: false };
|
||||
productionOrderNumber().then((data) => {
|
||||
this.dataForm = { ...this.dataForm, number: data.number };
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
|
@ -1,207 +0,0 @@
|
|||
<template>
|
||||
<div>
|
||||
<a-card title="生产计划">
|
||||
<a-row :gutter="[12, 8]">
|
||||
<a-col :span="24" style="width: 256px;">
|
||||
<a-range-picker @change="onChangePicker" />
|
||||
</a-col>
|
||||
<a-col :span="24" style="width: 200px;">
|
||||
<a-input v-model="searchForm.search" placeholder="生产单号, 销售单号" allowClear @pressEnter="search" />
|
||||
</a-col>
|
||||
<a-col :span="24" style="width: 84px;">
|
||||
<a-button type="primary" icon="search" @click="search">查询</a-button>
|
||||
</a-col>
|
||||
|
||||
<div style="margin-bottom: 12px; float: right">
|
||||
<a-button type="primary" icon="plus" style="margin: 0 8px" @click="openCreateModal({})">
|
||||
新增生产计划
|
||||
</a-button>
|
||||
</div>
|
||||
</a-row>
|
||||
|
||||
<a-row style="margin-top: 12px">
|
||||
<a-table
|
||||
rowKey="id"
|
||||
:columns="columns"
|
||||
:data-source="items"
|
||||
:pagination="pagination"
|
||||
:loading="loading"
|
||||
:scroll="{ x: 1600 }"
|
||||
>
|
||||
<div slot="action" slot-scope="value, item, index">
|
||||
<a-button-group size="small">
|
||||
<a-button v-if="item.status == 'in_plan'" @click="openCreateModal(item)">编辑</a-button>
|
||||
<a-button @click="detial(item)">详情</a-button>
|
||||
<a-popconfirm v-if="item.status == 'in_plan'" title="确定发布吗?" @confirm="issue(item)">
|
||||
<a-button type="primary">发布工单</a-button>
|
||||
</a-popconfirm>
|
||||
<a-popconfirm v-if="item.status == 'in_progress'" title="确定关闭吗?" @confirm="close(item)">
|
||||
<a-button type="primary">关闭工单</a-button>
|
||||
</a-popconfirm>
|
||||
<a-popconfirm v-if="item.status == 'in_plan'" title="确定删除吗?" @confirm="destroy(item)">
|
||||
<a-button type="danger">删除</a-button>
|
||||
</a-popconfirm>
|
||||
</a-button-group>
|
||||
</div>
|
||||
</a-table>
|
||||
</a-row>
|
||||
</a-card>
|
||||
|
||||
<form-modal v-model="visible" :form="targetItem" @create="create" @update="update" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
productionOrderList,
|
||||
productionOrderDelete,
|
||||
productionOrderIssue,
|
||||
productionOrderClose,
|
||||
} from "@/api/production";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
FormModal: () => import("./FormModal.vue"),
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
searchForm: { search: "", page: 1, ordering: undefined },
|
||||
pagination: { current: 1, total: 0, pageSize: 16 },
|
||||
loading: false,
|
||||
items: [],
|
||||
columns: [
|
||||
{
|
||||
title: "序号",
|
||||
dataIndex: "index",
|
||||
width: 60,
|
||||
fixed: "left",
|
||||
customRender: (value, item, index) => {
|
||||
return index + 1;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "生产计划单号",
|
||||
dataIndex: "number",
|
||||
fixed: "left",
|
||||
},
|
||||
{
|
||||
title: "销售单号",
|
||||
dataIndex: "sales_order_number",
|
||||
},
|
||||
{
|
||||
title: "状态",
|
||||
dataIndex: "status_display",
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: "产品编号",
|
||||
dataIndex: "goods_number",
|
||||
},
|
||||
{
|
||||
title: "产品名称",
|
||||
dataIndex: "goods_name",
|
||||
},
|
||||
{
|
||||
title: "计划数量",
|
||||
dataIndex: "total_quantity",
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: "完成数量",
|
||||
dataIndex: "quantity_produced",
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: "计划开始时间",
|
||||
dataIndex: "start_time",
|
||||
width: 180,
|
||||
},
|
||||
{
|
||||
title: "计划结束时间",
|
||||
dataIndex: "end_time",
|
||||
width: 180,
|
||||
},
|
||||
{
|
||||
title: "操作",
|
||||
dataIndex: "action",
|
||||
fixed: "right",
|
||||
width: 256,
|
||||
scopedSlots: { customRender: "action" },
|
||||
},
|
||||
],
|
||||
visible: false,
|
||||
targetItem: {},
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
initialize() {
|
||||
this.list();
|
||||
},
|
||||
list() {
|
||||
this.loading = true;
|
||||
productionOrderList(this.searchForm)
|
||||
.then((data) => {
|
||||
this.pagination.total = data.count;
|
||||
this.items = data.results;
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
create(item) {
|
||||
this.items = this.$functions.insertItem(this.items, item);
|
||||
},
|
||||
update(item) {
|
||||
this.items = this.$functions.replaceItem(this.items, item);
|
||||
},
|
||||
destroy(item) {
|
||||
productionOrderDelete({ id: item.id }).then(() => {
|
||||
this.$message.success("删除成功");
|
||||
this.items = this.$functions.removeItem(this.items, item);
|
||||
});
|
||||
},
|
||||
issue(item) {
|
||||
productionOrderIssue({ id: item.id }).then((data) => {
|
||||
this.$message.success("发布成功");
|
||||
this.items = this.$functions.replaceItem(this.items, data);
|
||||
});
|
||||
},
|
||||
close(item) {
|
||||
productionOrderClose({ id: item.id }).then((data) => {
|
||||
this.$message.success("关闭成功");
|
||||
this.items = this.$functions.replaceItem(this.items, data);
|
||||
});
|
||||
},
|
||||
detial(item) {
|
||||
this.$router.push({ path: "/production/detial", query: { id: item.id } });
|
||||
},
|
||||
search() {
|
||||
this.searchForm.page = 1;
|
||||
this.pagination.current = 1;
|
||||
this.list();
|
||||
},
|
||||
tableChange(pagination, filters, sorter) {
|
||||
this.searchForm.page = pagination.current;
|
||||
this.pagination.current = pagination.current;
|
||||
this.searchForm.ordering = `${sorter.order == "descend" ? "-" : ""}${sorter.field}`;
|
||||
this.list();
|
||||
},
|
||||
onChangePicker(date, dateString) {
|
||||
let startDate = date[0];
|
||||
let endDate = date[1];
|
||||
this.searchForm.start_date = startDate ? startDate.format("YYYY-MM-DD") : undefined;
|
||||
this.searchForm.end_date = endDate ? endDate.add(1, "days").format("YYYY-MM-DD") : undefined;
|
||||
this.search();
|
||||
},
|
||||
openCreateModal(item) {
|
||||
this.targetItem = { ...item };
|
||||
this.visible = true;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.initialize();
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
|
@ -1,124 +0,0 @@
|
|||
<template>
|
||||
<div>
|
||||
<a-card title="生产记录">
|
||||
<a-row :gutter="[12, 8]">
|
||||
<a-col :span="24" style="width: 256px;">
|
||||
<a-range-picker @change="onChangePicker" />
|
||||
</a-col>
|
||||
<a-col :span="24" style="width: 200px;">
|
||||
<a-input v-model="searchForm.search" placeholder="生产单号, 销售单号" allowClear @pressEnter="search" />
|
||||
</a-col>
|
||||
<a-col :span="24" style="width: 84px;">
|
||||
<a-button type="primary" icon="search" @click="search">查询</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row style="margin-top: 12px">
|
||||
<a-table
|
||||
rowKey="id"
|
||||
:columns="columns"
|
||||
:data-source="items"
|
||||
:pagination="pagination"
|
||||
:loading="loading"
|
||||
>
|
||||
</a-table>
|
||||
</a-row>
|
||||
</a-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { productionRecordList } from "@/api/production";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
searchForm: { search: "", page: 1, ordering: undefined },
|
||||
pagination: { current: 1, total: 0, pageSize: 16 },
|
||||
loading: false,
|
||||
items: [],
|
||||
columns: [
|
||||
{
|
||||
title: "序号",
|
||||
dataIndex: "index",
|
||||
width: 60,
|
||||
fixed: "left",
|
||||
customRender: (value, item, index) => {
|
||||
return index + 1;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "生产计划单号",
|
||||
dataIndex: "production_order_number",
|
||||
fixed: "left",
|
||||
},
|
||||
{
|
||||
title: "产品编号",
|
||||
dataIndex: "goods_number",
|
||||
},
|
||||
{
|
||||
title: "产品名称",
|
||||
dataIndex: "goods_name",
|
||||
},
|
||||
{
|
||||
title: "生产数量",
|
||||
dataIndex: "production_quantity",
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: "创建时间",
|
||||
dataIndex: "create_time",
|
||||
width: 180,
|
||||
},
|
||||
{
|
||||
title: "创建人",
|
||||
dataIndex: "creator_name",
|
||||
width: 180,
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
initialize() {
|
||||
this.list();
|
||||
},
|
||||
list() {
|
||||
this.loading = true;
|
||||
productionRecordList(this.searchForm)
|
||||
.then((data) => {
|
||||
this.pagination.total = data.count;
|
||||
this.items = data.results;
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
create() {
|
||||
this.list();
|
||||
},
|
||||
search() {
|
||||
this.searchForm.page = 1;
|
||||
this.pagination.current = 1;
|
||||
this.list();
|
||||
},
|
||||
tableChange(pagination, filters, sorter) {
|
||||
this.searchForm.page = pagination.current;
|
||||
this.pagination.current = pagination.current;
|
||||
this.searchForm.ordering = `${sorter.order == "descend" ? "-" : ""}${sorter.field}`;
|
||||
this.list();
|
||||
},
|
||||
onChangePicker(date, dateString) {
|
||||
let startDate = date[0];
|
||||
let endDate = date[1];
|
||||
this.searchForm.start_date = startDate ? startDate.format("YYYY-MM-DD") : undefined;
|
||||
this.searchForm.end_date = endDate ? endDate.add(1, "days").format("YYYY-MM-DD") : undefined;
|
||||
this.search();
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.initialize();
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
|
@ -1,66 +0,0 @@
|
|||
<template>
|
||||
<div>
|
||||
<a-modal v-model="visible" :confirmLoading="loading" :maskClosable="false" @cancel="cancel" @ok="confirm">
|
||||
<div slot="title">生产</div>
|
||||
<div>
|
||||
<a-form-model ref="form" :model="dataForm" :rules="rules" :label-col="{ span: 6 }" :wrapper-col="{ span: 16 }">
|
||||
<a-form-model-item prop="production_quantity" label="生产数量">
|
||||
<a-input-number v-model="dataForm.production_quantity" style="width: 100%;" />
|
||||
</a-form-model-item>
|
||||
</a-form-model>
|
||||
</div>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { productionRecordCreate } from "@/api/production";
|
||||
|
||||
export default {
|
||||
props: ["visible", "form"],
|
||||
model: { prop: "visible", event: "cancel" },
|
||||
data() {
|
||||
return {
|
||||
rules: {
|
||||
production_quantity: [{ required: true, message: "请输入生产数量", trigger: "change" }],
|
||||
},
|
||||
loading: false,
|
||||
dataForm: {},
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
confirm() {
|
||||
this.$refs.form.validate((valid) => {
|
||||
if (valid) {
|
||||
this.loading = true;
|
||||
productionRecordCreate(this.dataForm)
|
||||
.then((data) => {
|
||||
this.$message.success("生产成功");
|
||||
this.$emit("create", data);
|
||||
this.cancel();
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
cancel() {
|
||||
this.$emit("cancel", false);
|
||||
this.$refs.form.resetFields();
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
visible(value) {
|
||||
if (value) {
|
||||
this.dataForm = {
|
||||
production_order: this.form.id,
|
||||
production_quantity: this.form.remain_quantity,
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
|
@ -1,161 +0,0 @@
|
|||
<template>
|
||||
<div>
|
||||
<a-card title="生产任务">
|
||||
<a-row :gutter="[12, 8]">
|
||||
<a-col :span="24" style="width: 256px;">
|
||||
<a-range-picker @change="onChangePicker" />
|
||||
</a-col>
|
||||
<a-col :span="24" style="width: 200px;">
|
||||
<a-input v-model="searchForm.search" placeholder="生产单号, 销售单号" allowClear @pressEnter="search" />
|
||||
</a-col>
|
||||
<a-col :span="24" style="width: 84px;">
|
||||
<a-button type="primary" icon="search" @click="search">查询</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row style="margin-top: 12px">
|
||||
<a-table
|
||||
rowKey="id"
|
||||
:columns="columns"
|
||||
:data-source="items"
|
||||
:pagination="pagination"
|
||||
:loading="loading"
|
||||
:scroll="{ x: 1600 }"
|
||||
>
|
||||
<div slot="action" slot-scope="value, item, index">
|
||||
<a-button-group size="small">
|
||||
<a-button @click="detial()">详情</a-button>
|
||||
<a-button type="primary" @click="openCreateModal(item)">生产</a-button>
|
||||
</a-button-group>
|
||||
</div>
|
||||
</a-table>
|
||||
</a-row>
|
||||
</a-card>
|
||||
|
||||
<form-modal v-model="visible" :form="targetItem" @create="create" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { productionOrderList } from "@/api/production";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
FormModal: () => import("./FormModal.vue"),
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
searchForm: { search: "", page: 1, ordering: undefined, status: "in_progress" },
|
||||
pagination: { current: 1, total: 0, pageSize: 16 },
|
||||
loading: false,
|
||||
items: [],
|
||||
columns: [
|
||||
{
|
||||
title: "序号",
|
||||
dataIndex: "index",
|
||||
width: 60,
|
||||
fixed: "left",
|
||||
customRender: (value, item, index) => {
|
||||
return index + 1;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "生产计划单号",
|
||||
dataIndex: "number",
|
||||
fixed: "left",
|
||||
},
|
||||
{
|
||||
title: "销售单号",
|
||||
dataIndex: "sales_order_number",
|
||||
},
|
||||
{
|
||||
title: "产品编号",
|
||||
dataIndex: "goods_number",
|
||||
},
|
||||
{
|
||||
title: "产品名称",
|
||||
dataIndex: "goods_name",
|
||||
},
|
||||
{
|
||||
title: "计划数量",
|
||||
dataIndex: "total_quantity",
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: "完成数量",
|
||||
dataIndex: "quantity_produced",
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: "计划开始时间",
|
||||
dataIndex: "start_time",
|
||||
width: 180,
|
||||
},
|
||||
{
|
||||
title: "计划结束时间",
|
||||
dataIndex: "end_time",
|
||||
width: 180,
|
||||
},
|
||||
{
|
||||
title: "操作",
|
||||
dataIndex: "action",
|
||||
fixed: "right",
|
||||
width: 256,
|
||||
scopedSlots: { customRender: "action" },
|
||||
},
|
||||
],
|
||||
visible: false,
|
||||
targetItem: {},
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
initialize() {
|
||||
this.list();
|
||||
},
|
||||
list() {
|
||||
this.loading = true;
|
||||
productionOrderList(this.searchForm)
|
||||
.then((data) => {
|
||||
this.pagination.total = data.count;
|
||||
this.items = data.results;
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
create() {
|
||||
this.list();
|
||||
},
|
||||
detial(item) {
|
||||
this.$router.push({ path: "/production/detial", query: { id: item.id } });
|
||||
},
|
||||
search() {
|
||||
this.searchForm.page = 1;
|
||||
this.pagination.current = 1;
|
||||
this.list();
|
||||
},
|
||||
tableChange(pagination, filters, sorter) {
|
||||
this.searchForm.page = pagination.current;
|
||||
this.pagination.current = pagination.current;
|
||||
this.searchForm.ordering = `${sorter.order == "descend" ? "-" : ""}${sorter.field}`;
|
||||
this.list();
|
||||
},
|
||||
onChangePicker(date, dateString) {
|
||||
let startDate = date[0];
|
||||
let endDate = date[1];
|
||||
this.searchForm.start_date = startDate ? startDate.format("YYYY-MM-DD") : undefined;
|
||||
this.searchForm.end_date = endDate ? endDate.add(1, "days").format("YYYY-MM-DD") : undefined;
|
||||
this.search();
|
||||
},
|
||||
openCreateModal(item) {
|
||||
this.targetItem = { ...item };
|
||||
this.visible = true;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.initialize();
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
|
@ -1,3 +0,0 @@
|
|||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
|
@ -1,6 +0,0 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class ProductionConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'apps.production'
|
|
@ -1,26 +0,0 @@
|
|||
from django_filters.rest_framework import FilterSet
|
||||
from django_filters.filters import *
|
||||
from apps.production.models import *
|
||||
|
||||
|
||||
class ProductionOrderFilter(FilterSet):
|
||||
start_date = DateFilter(field_name='create_time', lookup_expr='gte', label='开始日期')
|
||||
end_date = DateFilter(field_name='create_time', lookup_expr='lt', label='结束日期')
|
||||
|
||||
class Meta:
|
||||
model = ProductionOrder
|
||||
fields = ['sales_order', 'goods', 'status', 'creator', 'start_date', 'end_date']
|
||||
|
||||
|
||||
class ProductionRecordFilter(FilterSet):
|
||||
start_date = DateFilter(field_name='create_time', lookup_expr='gte', label='开始日期')
|
||||
end_date = DateFilter(field_name='create_time', lookup_expr='lt', label='结束日期')
|
||||
|
||||
class Meta:
|
||||
model = ProductionRecord
|
||||
fields = ['production_order', 'goods', 'creator', 'start_date', 'end_date']
|
||||
|
||||
|
||||
__all__ = [
|
||||
'ProductionOrderFilter', 'ProductionRecordFilter',
|
||||
]
|
|
@ -1,61 +0,0 @@
|
|||
from extensions.common.base import *
|
||||
from extensions.models import *
|
||||
|
||||
|
||||
class ProductionOrder(Model):
|
||||
"""生产单据"""
|
||||
|
||||
class Status(TextChoices):
|
||||
IN_PLAN = ('in_plan', '计划中')
|
||||
IN_PROGRESS = ('in_progress', '进行中')
|
||||
COMPLETED = ('completed', '已完成')
|
||||
CLOSED = ('closed', '强制关闭')
|
||||
|
||||
number = CharField(max_length=32, verbose_name='编号')
|
||||
is_related = BooleanField(default=False, verbose_name='关联状态')
|
||||
sales_order = ForeignKey(
|
||||
'sales.SalesOrder', on_delete=PROTECT, null=True, related_name='production_orders', verbose_name='销售单')
|
||||
goods = ForeignKey('goods.Goods', on_delete=PROTECT, related_name='production_orders', verbose_name='产品')
|
||||
total_quantity = FloatField(verbose_name='生产总数')
|
||||
quantity_produced = FloatField(verbose_name='已生产数量')
|
||||
remain_quantity = FloatField(verbose_name='剩余数量')
|
||||
stock_in_quantity = FloatField(default=0, verbose_name='入库数量')
|
||||
start_time = DateTimeField(null=True, verbose_name='开始时间')
|
||||
end_time = DateTimeField(null=True, verbose_name='结束时间')
|
||||
status = CharField(max_length=32, choices=Status.choices, default=Status.IN_PLAN, verbose_name='状态')
|
||||
creator = ForeignKey('system.User', on_delete=PROTECT,
|
||||
related_name='created_production_orders', verbose_name='创建人')
|
||||
create_time = DateTimeField(auto_now_add=True, verbose_name='创建时间')
|
||||
team = ForeignKey('system.Team', on_delete=CASCADE, related_name='production_orders')
|
||||
|
||||
@classmethod
|
||||
def get_number(cls, team):
|
||||
start_date, end_date = pendulum.today().to_datetime_string(), pendulum.tomorrow().to_datetime_string()
|
||||
instance = cls.objects.filter(team=team, create_time__gte=start_date, create_time__lt=end_date).last()
|
||||
|
||||
try:
|
||||
result = re.match('^(.*?)([1-9]+)$', instance.number)
|
||||
number = result.group(1) + str(int(result.group(2)) + 1)
|
||||
except AttributeError:
|
||||
number = 'SC' + pendulum.today().format('YYYYMMDD') + '0001'
|
||||
|
||||
return number
|
||||
|
||||
|
||||
class ProductionRecord(Model):
|
||||
"""生产记录"""
|
||||
|
||||
production_order = ForeignKey(
|
||||
'production.ProductionOrder', on_delete=CASCADE, related_name='production_records', verbose_name='生产单')
|
||||
goods = ForeignKey(
|
||||
'goods.Goods', on_delete=PROTECT, related_name='production_records', verbose_name='产品')
|
||||
production_quantity = FloatField(verbose_name='生产数量')
|
||||
creator = ForeignKey(
|
||||
'system.User', on_delete=PROTECT, related_name='created_production_records', verbose_name='创建人')
|
||||
create_time = DateTimeField(auto_now_add=True, verbose_name='创建时间')
|
||||
team = ForeignKey('system.Team', on_delete=CASCADE, related_name='production_records')
|
||||
|
||||
|
||||
__all__ = [
|
||||
'ProductionOrder', 'ProductionRecord',
|
||||
]
|
|
@ -1,14 +0,0 @@
|
|||
from extensions.permissions import ModelPermission
|
||||
|
||||
|
||||
class ProductionOrderPermission(ModelPermission):
|
||||
code = 'production_order'
|
||||
|
||||
|
||||
class ProductionRecordPermission(ModelPermission):
|
||||
code = 'production_record'
|
||||
|
||||
|
||||
__all__ = [
|
||||
'ProductionOrderPermission', 'ProductionRecordPermission',
|
||||
]
|
|
@ -1,15 +0,0 @@
|
|||
from extensions.serializers import *
|
||||
|
||||
|
||||
class NumberResponse(Serializer):
|
||||
number = CharField(label='编号')
|
||||
|
||||
|
||||
class ProductionStockInRequest(Serializer):
|
||||
warehouse = IntegerField(label='仓库')
|
||||
stock_in_quantity = FloatField(label='入库数量')
|
||||
|
||||
|
||||
__all__ = [
|
||||
'NumberResponse', 'ProductionStockInRequest',
|
||||
]
|
|
@ -1,104 +0,0 @@
|
|||
from extensions.common.base import *
|
||||
from extensions.serializers import *
|
||||
from extensions.exceptions import *
|
||||
from apps.production.models import *
|
||||
from apps.sales.models import *
|
||||
from apps.goods.models import *
|
||||
|
||||
|
||||
class ProductionOrderSerializer(BaseSerializer):
|
||||
"""生产单据"""
|
||||
|
||||
class SalesGoodsItemSerializer(ModelSerializer):
|
||||
goods_number = CharField(source='goods.number', read_only=True, label='产品编号')
|
||||
goods_name = CharField(source='goods.name', read_only=True, label='产品名称')
|
||||
goods_barcode = CharField(source='goods.barcode', read_only=True, label='产品条码')
|
||||
unit_name = CharField(source='goods.unit.name', read_only=True, label='单位名称')
|
||||
|
||||
class Meta:
|
||||
model = SalesGoods
|
||||
fields = ['id', 'goods', 'goods_number', 'goods_name', 'goods_barcode', 'sales_quantity',
|
||||
'sales_price', 'total_amount', 'return_quantity', 'unit_name']
|
||||
|
||||
sales_order_number = CharField(source='sales_order.number', read_only=True, label='销售单号')
|
||||
sales_goods_items = SalesGoodsItemSerializer(
|
||||
source='sales_order.sales_goods_set', many=True, read_only=True, label='销售产品Item')
|
||||
goods_number = CharField(source='goods.number', read_only=True, label='产品编号')
|
||||
goods_name = CharField(source='goods.name', read_only=True, label='产品名称')
|
||||
status_display = CharField(source='get_status_display', read_only=True, label='状态')
|
||||
creator_name = CharField(source='creator.name', read_only=True, label='创建人名称')
|
||||
|
||||
class Meta:
|
||||
model = ProductionOrder
|
||||
read_only_fields = ['id', 'sales_order_number', 'sales_goods_items', 'remain_quantity', 'stock_in_quantity',
|
||||
'goods_number', 'goods_name', 'quantity_produced', 'remain_quantity', 'status', 'status_display',
|
||||
'creator', 'creator_name', 'create_time']
|
||||
fields = ['number', 'is_related', 'sales_order', 'goods', 'total_quantity',
|
||||
'start_time', 'end_time', *read_only_fields]
|
||||
|
||||
def validate_number(self, value):
|
||||
self.validate_unique({'number': value}, message=f'编号[{value}]已存在')
|
||||
return value
|
||||
|
||||
def validate_sales_order(self, instance):
|
||||
instance = self.validate_foreign_key(SalesOrder, instance, message='销售单不存在')
|
||||
return instance
|
||||
|
||||
def validate_goods(self, instance):
|
||||
instance = self.validate_foreign_key(Goods, instance, message='产品不存在')
|
||||
return instance
|
||||
|
||||
def validate_total_quantity(self, value):
|
||||
if value <= 0:
|
||||
raise ValidationError('生产数量小于等于零')
|
||||
return value
|
||||
|
||||
def validate(self, attrs):
|
||||
if attrs['is_related'] and not attrs['sales_order']:
|
||||
raise ValidationError('未关联销售单')
|
||||
return super().validate(attrs)
|
||||
|
||||
def create(self, validated_data):
|
||||
validated_data['quantity_produced'] = 0
|
||||
validated_data['creator'] = self.user
|
||||
return super().create(validated_data)
|
||||
|
||||
def save(self, **kwargs):
|
||||
kwargs['remain_quantity'] = self.validated_data['total_quantity']
|
||||
return super().save(**kwargs)
|
||||
|
||||
|
||||
class ProductionRecordSerializer(BaseSerializer):
|
||||
"""生产记录"""
|
||||
|
||||
production_order_number = CharField(source='production_order.number', read_only=True, label='生产单号')
|
||||
goods_number = CharField(source='goods.number', read_only=True, label='产品编号')
|
||||
goods_name = CharField(source='goods.name', read_only=True, label='产品名称')
|
||||
creator_name = CharField(source='creator.name', read_only=True, label='创建人名称')
|
||||
|
||||
class Meta:
|
||||
model = ProductionRecord
|
||||
read_only_fields = ['id', 'production_order_number', 'goods', 'goods_number', 'goods_name', 'creator',
|
||||
'creator_name', 'create_time']
|
||||
fields = ['production_order', 'production_quantity', *read_only_fields]
|
||||
|
||||
def validate_production_order(self, instance):
|
||||
instance = self.validate_foreign_key(ProductionOrder, instance, message='生产单不存在')
|
||||
if instance.status != ProductionOrder.Status.IN_PROGRESS:
|
||||
raise ValidationError(f'工单{instance.number}{instance.get_status_display()}, 无法创建')
|
||||
return instance
|
||||
|
||||
def validate_production_quantity(self, value):
|
||||
if value <= 0:
|
||||
raise ValidationError('生产数量小于等于零')
|
||||
return value
|
||||
|
||||
def create(self, validated_data):
|
||||
validated_data['goods'] = validated_data['production_order'].goods
|
||||
validated_data['creator'] = self.user
|
||||
return super().create(validated_data)
|
||||
|
||||
|
||||
__all__ = [
|
||||
'ProductionOrderSerializer', 'ProductionRecordSerializer',
|
||||
]
|
|
@ -1,3 +0,0 @@
|
|||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
|
@ -1,8 +0,0 @@
|
|||
from extensions.routers import *
|
||||
from apps.production.views import *
|
||||
|
||||
|
||||
router = BaseRouter()
|
||||
router.register('production_orders', ProductionOrderViewSet, 'production_order')
|
||||
router.register('production_records', ProductionRecordViewSet, 'production_record')
|
||||
urlpatterns = router.urls
|
|
@ -1,145 +0,0 @@
|
|||
from extensions.common.schema import *
|
||||
from extensions.common.base import *
|
||||
from extensions.permissions import *
|
||||
from extensions.exceptions import *
|
||||
from extensions.viewsets import *
|
||||
from apps.production.serializers import *
|
||||
from apps.production.permissions import *
|
||||
from apps.production.filters import *
|
||||
from apps.production.schemas import *
|
||||
from apps.production.models import *
|
||||
from apps.stock_in.models import *
|
||||
from apps.data.models import *
|
||||
|
||||
|
||||
class ProductionOrderViewSet(ModelViewSet):
|
||||
"""生产单据"""
|
||||
|
||||
serializer_class = ProductionOrderSerializer
|
||||
permission_classes = [IsAuthenticated, ProductionOrderPermission]
|
||||
filterset_class = ProductionOrderFilter
|
||||
search_fields = ['number', 'sales_order__number', 'goods__number', 'goods__name']
|
||||
ordering_fields = ['number', 'total_quantity', 'quantity_produced', 'remain_quantity',
|
||||
'start_time', 'end_time', 'create_time']
|
||||
select_related_fields = ['sales_order', 'goods', 'creator']
|
||||
prefetch_related_fields = ['sales_order__sales_goods_set']
|
||||
queryset = ProductionOrder.objects.all()
|
||||
|
||||
def perform_update(self, serializer):
|
||||
instance = serializer.instance
|
||||
if instance.status != ProductionOrder.Status.IN_PLAN:
|
||||
raise ValidationError(f'工单{instance.number}{instance.get_status_display()}, 无法编辑')
|
||||
return super().perform_update(serializer)
|
||||
|
||||
def perform_destroy(self, instance):
|
||||
if instance.status != ProductionOrder.Status.IN_PLAN:
|
||||
raise ValidationError(f'工单{instance.number}{instance.get_status_display()}, 无法删除')
|
||||
return super().perform_destroy(instance)
|
||||
|
||||
@extend_schema(responses={200: NumberResponse})
|
||||
@action(detail=False, methods=['get'])
|
||||
def number(self, request, *args, **kwargs):
|
||||
"""获取编号"""
|
||||
|
||||
number = ProductionOrder.get_number(self.team)
|
||||
return Response(data={'number': number}, status=status.HTTP_200_OK)
|
||||
|
||||
@extend_schema(responses={200: ProductionOrderSerializer})
|
||||
@action(detail=True, methods=['post'])
|
||||
def issue(self, request, *args, **kwargs):
|
||||
"""发布工单"""
|
||||
|
||||
instance = self.get_object()
|
||||
if instance.status != ProductionOrder.Status.IN_PLAN:
|
||||
raise ValidationError(f'工单{instance.number}{instance.get_status_display()}, 无法发布工单')
|
||||
|
||||
instance.status = ProductionOrder.Status.IN_PROGRESS
|
||||
instance.save(update_fields=['status'])
|
||||
|
||||
serializer = ProductionOrderSerializer(instance=instance)
|
||||
return Response(data=serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
@extend_schema(responses={200: ProductionOrderSerializer})
|
||||
@action(detail=True, methods=['post'])
|
||||
def close(self, request, *args, **kwargs):
|
||||
"""关闭工单"""
|
||||
|
||||
instance = self.get_object()
|
||||
if instance.status != ProductionOrder.Status.IN_PROGRESS:
|
||||
raise ValidationError(f'工单{instance.number}{instance.get_status_display()}, 无法关闭工单')
|
||||
|
||||
instance.status = ProductionOrder.Status.CLOSED
|
||||
instance.save(update_fields=['status'])
|
||||
|
||||
serializer = ProductionOrderSerializer(instance=instance)
|
||||
return Response(data=serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
@extend_schema(request=ProductionStockInRequest, responses={200: ProductionOrderSerializer})
|
||||
@action(detail=True, methods=['post'])
|
||||
def stock_in(self, request, *args, **kwargs):
|
||||
"""入库"""
|
||||
|
||||
request_serializer = ProductionStockInRequest(data=request.data)
|
||||
request_serializer.is_valid(raise_exception=True)
|
||||
validated_data = request_serializer.validated_data
|
||||
warehouse_id, stock_in_quantity = validated_data['warehouse'], validated_data['stock_in_quantity']
|
||||
|
||||
if not (warehouse := Warehouse.objects.filter(id=warehouse_id, team=self.team).first()):
|
||||
raise ValidationError(f'仓库不存在')
|
||||
|
||||
production_order = self.get_object()
|
||||
if production_order.status != ProductionOrder.Status.IN_PROGRESS:
|
||||
raise ValidationError(f'工单{production_order.number}{production_order.get_status_display()}, 无法入库')
|
||||
|
||||
# 创建入库单据
|
||||
stock_in_order_number = StockInOrder.get_number(team=self.team)
|
||||
stock_in_order = StockInOrder.objects.create(
|
||||
number=stock_in_order_number, warehouse=warehouse, type=StockInOrder.Type.PRODUCTION,
|
||||
production_order=production_order, total_quantity=stock_in_quantity, remain_quantity=stock_in_quantity,
|
||||
creator=self.user, team=self.team)
|
||||
|
||||
# 创建入库产品
|
||||
StockInGoods.objects.create(
|
||||
stock_in_order=stock_in_order, goods=production_order.goods, stock_in_quantity=stock_in_quantity,
|
||||
remain_quantity=stock_in_quantity, team=self.team)
|
||||
|
||||
# 同步生产单据
|
||||
production_order.stock_in_quantity = NP.plus(production_order.stock_in_quantity, stock_in_quantity)
|
||||
production_order.save(update_fields=['stock_in_quantity'])
|
||||
|
||||
serializer = ProductionOrderSerializer(instance=production_order)
|
||||
return Response(data=serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
class ProductionRecordViewSet(BaseViewSet, ListModelMixin, RetrieveModelMixin, CreateModelMixin):
|
||||
"""生产记录"""
|
||||
|
||||
serializer_class = ProductionRecordSerializer
|
||||
permission_classes = [IsAuthenticated, ProductionRecordPermission]
|
||||
filterset_class = ProductionRecordFilter
|
||||
search_fields = ['production_order__number', 'goods__number', 'goods__name']
|
||||
ordering_fields = ['production_quantity', 'create_time']
|
||||
select_related_fields = ['production_order', 'goods', 'creator']
|
||||
queryset = ProductionRecord.objects.all()
|
||||
|
||||
@transaction.atomic
|
||||
def perform_create(self, serializer):
|
||||
validated_data = serializer.validated_data
|
||||
production_order = validated_data['production_order']
|
||||
production_quantity = validated_data['production_quantity']
|
||||
|
||||
if production_order.remain_quantity < production_quantity:
|
||||
raise ValidationError('生产数量错误')
|
||||
|
||||
production_order.quantity_produced = NP.plus(production_order.quantity_produced, production_quantity)
|
||||
production_order.remain_quantity = NP.minus(production_order.remain_quantity, production_quantity)
|
||||
if production_order.remain_quantity == 0:
|
||||
production_order.status = ProductionOrder.Status.COMPLETED
|
||||
production_order.save(update_fields=['quantity_produced', 'remain_quantity', 'status'])
|
||||
|
||||
serializer.save()
|
||||
|
||||
|
||||
__all__ = [
|
||||
'ProductionOrderViewSet', 'ProductionRecordViewSet',
|
||||
]
|
|
@ -11,7 +11,6 @@ class StockInOrder(Model):
|
|||
PURCHASE = ('purchase', '采购')
|
||||
SALES_RETURN = ('sales_return', '销售退货')
|
||||
STOCK_TRANSFER = ('stock_transfer', '调拨')
|
||||
PRODUCTION = ('production', '生产')
|
||||
|
||||
number = CharField(max_length=32, verbose_name='编号')
|
||||
warehouse = ForeignKey('data.Warehouse', on_delete=PROTECT,
|
||||
|
@ -23,8 +22,6 @@ class StockInOrder(Model):
|
|||
related_name='stock_in_order', verbose_name='销售退货单据')
|
||||
stock_transfer_order = OneToOneField('stock_transfer.StockTransferOrder', on_delete=CASCADE, null=True,
|
||||
related_name='stock_in_order', verbose_name='调拨单据')
|
||||
production_order = OneToOneField('production.ProductionOrder', on_delete=CASCADE, null=True,
|
||||
related_name='stock_in_order', verbose_name='生产单据')
|
||||
total_quantity = FloatField(verbose_name='入库总数')
|
||||
remain_quantity = FloatField(default=0, verbose_name='入库剩余数量')
|
||||
is_completed = BooleanField(default=False, verbose_name='入库完成状态')
|
||||
|
|
|
@ -34,8 +34,6 @@ class StockInOrderSerializer(BaseSerializer):
|
|||
source='sales_return_order.number', read_only=True, label='销售退货单据编号')
|
||||
stock_transfer_order_number = CharField(
|
||||
source='stock_transfer_order.number', read_only=True, label='调拨单据编号')
|
||||
production_order_number = CharField(
|
||||
source='production_order.number', read_only=True, label='生产单据编号')
|
||||
creator_name = CharField(source='creator.name', read_only=True, label='创建人名称')
|
||||
stock_in_goods_items = StockInGoodsItemSerializer(
|
||||
source='stock_in_goods_set', many=True, label='入库产品Item')
|
||||
|
@ -45,7 +43,6 @@ class StockInOrderSerializer(BaseSerializer):
|
|||
fields = ['id', 'number', 'warehouse', 'warehouse_number', 'warehouse_name', 'type',
|
||||
'type_display', 'purchase_order', 'purchase_order_number', 'sales_return_order',
|
||||
'sales_return_order_number', 'stock_transfer_order', 'stock_transfer_order_number',
|
||||
'production_order', 'production_order_number',
|
||||
'total_quantity', 'remain_quantity', 'is_completed', 'is_void', 'creator',
|
||||
'creator_name', 'create_time', 'stock_in_goods_items']
|
||||
|
||||
|
|
26
erp_mobile/.gitignore
vendored
|
@ -1,26 +0,0 @@
|
|||
.DS_Store
|
||||
node_modules
|
||||
/dist
|
||||
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Log files
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
.vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
unpackage/*
|
||||
!unpackage/res
|
|
@ -1,16 +0,0 @@
|
|||
{ // launch.json 配置了启动调试时相关设置,configurations下节点名称可为 app-plus/h5/mp-weixin/mp-baidu/mp-alipay/mp-qq/mp-toutiao/mp-360/
|
||||
// launchtype项可配置值为local或remote, local代表前端连本地云函数,remote代表前端连云端云函数
|
||||
"version": "0.0",
|
||||
"configurations": [{
|
||||
"app-plus" :
|
||||
{
|
||||
"launchtype" : "local"
|
||||
},
|
||||
"default" :
|
||||
{
|
||||
"launchtype" : "local"
|
||||
},
|
||||
"type" : "uniCloud"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
<style lang="scss">
|
||||
/* 注意要写在第一行,同时给style标签加入lang="scss"属性 */
|
||||
@import "@/uni_modules/uview-ui/index.scss";
|
||||
</style>
|
||||
|
||||
<script>
|
||||
var globalEvent = uni.requireNativePlugin('globalEvent');
|
||||
|
||||
export default {
|
||||
onLaunch: function() {
|
||||
console.log('App Launch')
|
||||
|
||||
globalEvent.addEventListener('onPrintCallback', function(e) {
|
||||
console.log(e.key)
|
||||
if (e.key == 0) {
|
||||
uni.showToast({
|
||||
title: '打印成功',
|
||||
duration: 2000
|
||||
});
|
||||
} else if (e.key == 3) {
|
||||
uni.showToast({
|
||||
title: '缺纸',
|
||||
duration: 2000
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
onShow: function() {
|
||||
console.log('App Show')
|
||||
},
|
||||
onHide: function() {
|
||||
console.log('App Hide')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/*每个页面公共css */
|
||||
</style>
|
|
@ -1,674 +0,0 @@
|
|||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
|
@ -1,18 +0,0 @@
|
|||
# 海鸥云ERP_PDA移动端
|
||||
|
||||
### 介绍
|
||||
海鸥云ERP移动端,采用前后端分离技术,api使用restful协议,方便二次开发,后端使用Python,Django,DRF等技术,移动端使用uniapp进行构建,主要包含仓库操作人员的扫码操作流程
|
||||
|
||||
### 软件说明
|
||||
该软件为海鸥云ERP的PDA端代码,PC及后端代码请参考仓库
|
||||
* Gitee地址: [Gitee](https://gitee.com/haioucloud/erp)
|
||||
* Demo地址: [Demo](http://114.218.158.78:12222/) 公司编号: admin 测试帐号:admin 密码:admin
|
||||
|
||||
### 使用前须知
|
||||
* 软件开放源码(发行协议:GPL-3.0),用户可免费使用,但禁止任何单位或个人修改软件后再次发行的行为。商业使用需得到我司商业授权。
|
||||
* 我们欢迎对开源技术感兴趣的朋友一起加入到我们项目中来完善系统功能并为客户提供服务。欢迎扫描下方二维码添加技术交流群,添加时请备注来意
|
||||
|
||||
![微信群](https://gitee.com/haioucloud/erp/raw/master/raw/%E5%BE%AE%E4%BF%A1%E7%BE%A4.png)
|
||||
* 功能定制或代理授权咨询:Tel:18761717855或扫描下方二维码联系
|
||||
|
||||
![微信](https://gitee.com/haioucloud/erp/raw/master/raw/%E5%BE%AE%E4%BF%A1.png)
|
|
@ -1,31 +0,0 @@
|
|||
import request from "@/utils/request.js";
|
||||
|
||||
// UserOption
|
||||
export function userOption(data) {
|
||||
return request({ url: `/users/options/`, method: 'get', data })
|
||||
}
|
||||
|
||||
// Warehouse
|
||||
export function warehouseOption(data) {
|
||||
return request({ url: `/warehouses/options/`, method: 'get', data })
|
||||
}
|
||||
|
||||
// BatchOption
|
||||
export function batchOption(data) {
|
||||
return request({ url: `/batchs/options/`, method: 'get', data })
|
||||
}
|
||||
|
||||
// InventoryOption
|
||||
export function inventoryOption(data) {
|
||||
return request({ url: `/inventories/options/`, method: 'get', data })
|
||||
}
|
||||
|
||||
// Category
|
||||
export function categoryOption(data) {
|
||||
return request({ url: `/goods_categories/options/`, method: 'get', data })
|
||||
}
|
||||
|
||||
// Unit
|
||||
export function unitOption(data) {
|
||||
return request({ url: `/goods_units/options/`, method: 'get', data })
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
import request from "@/utils/request.js";
|
||||
|
||||
|
||||
// Product
|
||||
export function productList(data) {
|
||||
return request({ url: '/goods/', method: 'get', data });
|
||||
}
|
||||
|
||||
export function productCreate(data) {
|
||||
return request({ url: '/goods/', method: 'post', data });
|
||||
}
|
||||
|
||||
export function productUpdate(data) {
|
||||
return request({ url: `/goods/${data.id}/`, method: 'put', data });
|
||||
}
|
||||
|
||||
export function productDestroy(data) {
|
||||
return request({ url: `/goods/${data.id}/`, method: 'delete', data });
|
||||
}
|
||||
|
||||
export function productNumber(data) {
|
||||
return request({ url: '/goods/number/', method: 'get', data });
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
import request from "@/utils/request.js";
|
||||
|
||||
// 批次报表
|
||||
export function batchReportList(data) {
|
||||
return request({ url: `/batchs/`, method: 'get', data });
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
import request from "@/utils/request.js";
|
||||
|
||||
|
||||
export function stockCheckNumber(params) {
|
||||
return request({ url: `/stock_check_orders/number/`, method: 'get', params })
|
||||
}
|
||||
|
||||
export function stockCheckCreate(data) {
|
||||
return request({ url: `/stock_check_orders/`, method: 'post', data })
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
import request from "@/utils/request.js";
|
||||
|
||||
// 入库任务
|
||||
export function stockInTaskList(data) {
|
||||
return request({ url: `/stock_in_orders/`, method: 'get', data });
|
||||
}
|
||||
|
||||
// 入库记录
|
||||
export function stockInRecordCreate(data) {
|
||||
return request({ url: `/stock_in_records/`, method: 'post', data });
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
import request from "@/utils/request.js";
|
||||
|
||||
// 出库任务
|
||||
export function stockOutTaskList(data) {
|
||||
return request({ url: `/stock_out_orders/`, method: 'get', data });
|
||||
}
|
||||
|
||||
// 出库记录
|
||||
export function stockOutRecordCreate(data) {
|
||||
return request({ url: `/stock_out_records/`, method: 'post', data });
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
import request from "@/utils/request.js";
|
||||
|
||||
|
||||
// 获取令牌
|
||||
export function getToken(data) {
|
||||
return request({ url: '/user/get_token/', method: 'post', header: {}, data });
|
||||
}
|
||||
|
||||
// 获取用户信息
|
||||
export function getInfo(data) {
|
||||
return request({ url: '/user/info/', method: 'get', data });
|
||||
}
|
||||
|
||||
// 刷新令牌
|
||||
export function refreshToken(data) {
|
||||
return request({ url: '/user/refresh_token/', method: 'post', header: {}, data });
|
||||
}
|
||||
|
||||
// 首页概览
|
||||
export function homeOverview(data) {
|
||||
return request({ url: `/home_overview/`, method: "get", data });
|
||||
}
|
|
@ -1,75 +0,0 @@
|
|||
<template>
|
||||
<u-modal :show="show" title="打印次数" :showCancelButton="true" confirmText="打印" @cancel="cancel" @confirm="confirm">
|
||||
<view class="slot-content">
|
||||
<view>
|
||||
<u--input type="digit" border="surround" :focus="true" v-model="printTimes"></u--input>
|
||||
</view>
|
||||
</view>
|
||||
</u-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const printer = uni.requireNativePlugin('LcPrinter');
|
||||
|
||||
export default {
|
||||
props: ["show", "number", "name"],
|
||||
data() {
|
||||
return {
|
||||
printTimes: 1,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
initPrinter() {
|
||||
printer.initPrinter({});
|
||||
printer.printEnableMark({ enable: true });
|
||||
printer.setFontSize({ fontSize: 8 });
|
||||
},
|
||||
confirm() {
|
||||
if (printer === undefined) {
|
||||
uni.showToast({
|
||||
title: '打印失败,请联系软件厂商购买指定型号PDA',
|
||||
duration: 2000
|
||||
});
|
||||
}
|
||||
|
||||
for (let index = 0; index < this.printTimes; index++) {
|
||||
printer.printLine({ line_length: 1 })
|
||||
printer.printBarcode2({
|
||||
offset: 1,
|
||||
text: this.number,
|
||||
height: 150,
|
||||
barcodeType: 73,
|
||||
hriPosition: 1,
|
||||
});
|
||||
printer.printLine({ line_length: 1 })
|
||||
printer.printText2({
|
||||
offset: 2,
|
||||
fontSize: 3,
|
||||
isBold: false,
|
||||
isUnderLine: false,
|
||||
content: `编号: ${this.number}\n`
|
||||
});
|
||||
printer.printText2({
|
||||
offset: 2,
|
||||
fontSize: 3,
|
||||
isBold: false,
|
||||
isUnderLine: false,
|
||||
content: `名称: ${this.name}`
|
||||
});
|
||||
printer.printGoToNextMark();
|
||||
}
|
||||
this.cancel();
|
||||
},
|
||||
cancel() {
|
||||
this.printTimes = 1;
|
||||
this.$emit("cancel", false);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.initPrinter();
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
|
@ -1,20 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<script>
|
||||
var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') ||
|
||||
CSS.supports('top: constant(a)'))
|
||||
document.write(
|
||||
'<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
|
||||
(coverSupport ? ', viewport-fit=cover' : '') + '" />')
|
||||
</script>
|
||||
<title></title>
|
||||
<!--preload-links-->
|
||||
<!--app-context-->
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"><!--app-html--></div>
|
||||
<script type="module" src="/main.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -1,30 +0,0 @@
|
|||
import App from './App';
|
||||
import store from 'store';
|
||||
|
||||
// #ifndef VUE3
|
||||
import Vue from 'vue'
|
||||
import uView from '@/uni_modules/uview-ui'
|
||||
Vue.use(uView)
|
||||
|
||||
|
||||
Vue.config.productionTip = false
|
||||
Vue.prototype.$store = store;
|
||||
|
||||
App.mpType = 'app'
|
||||
const app = new Vue({
|
||||
store,
|
||||
...App
|
||||
})
|
||||
app.$mount()
|
||||
// #endif
|
||||
|
||||
// #ifdef VUE3
|
||||
import { createSSRApp } from 'vue'
|
||||
export function createApp() {
|
||||
const app = createSSRApp(App)
|
||||
return {
|
||||
app
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
|
|
@ -1,122 +0,0 @@
|
|||
{
|
||||
"name" : "海鸥云ERP",
|
||||
"appid" : "__UNI__EF5FEA2",
|
||||
"description" : "",
|
||||
"versionName" : "1.0.0",
|
||||
"versionCode" : "100",
|
||||
"transformPx" : false,
|
||||
/* 5+App特有相关 */
|
||||
"app-plus" : {
|
||||
"usingComponents" : true,
|
||||
"nvueStyleCompiler" : "uni-app",
|
||||
"compilerVersion" : 3,
|
||||
"splashscreen" : {
|
||||
"alwaysShowBeforeRender" : true,
|
||||
"waiting" : true,
|
||||
"autoclose" : true,
|
||||
"delay" : 0
|
||||
},
|
||||
/* 模块配置 */
|
||||
"modules" : {},
|
||||
/* 应用发布信息 */
|
||||
"distribute" : {
|
||||
/* android打包配置 */
|
||||
"android" : {
|
||||
"permissions" : [
|
||||
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
|
||||
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
|
||||
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
|
||||
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
|
||||
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
|
||||
"<uses-feature android:name=\"android.hardware.camera\"/>",
|
||||
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
|
||||
]
|
||||
},
|
||||
/* ios打包配置 */
|
||||
"ios" : {},
|
||||
/* SDK配置 */
|
||||
"sdkConfigs" : {
|
||||
"ad" : {}
|
||||
},
|
||||
"icons" : {
|
||||
"android" : {
|
||||
"hdpi" : "unpackage/res/icons/72x72.png",
|
||||
"xhdpi" : "unpackage/res/icons/96x96.png",
|
||||
"xxhdpi" : "unpackage/res/icons/144x144.png",
|
||||
"xxxhdpi" : "unpackage/res/icons/192x192.png"
|
||||
},
|
||||
"ios" : {
|
||||
"appstore" : "unpackage/res/icons/1024x1024.png",
|
||||
"ipad" : {
|
||||
"app" : "unpackage/res/icons/76x76.png",
|
||||
"app@2x" : "unpackage/res/icons/152x152.png",
|
||||
"notification" : "unpackage/res/icons/20x20.png",
|
||||
"notification@2x" : "unpackage/res/icons/40x40.png",
|
||||
"proapp@2x" : "unpackage/res/icons/167x167.png",
|
||||
"settings" : "unpackage/res/icons/29x29.png",
|
||||
"settings@2x" : "unpackage/res/icons/58x58.png",
|
||||
"spotlight" : "unpackage/res/icons/40x40.png",
|
||||
"spotlight@2x" : "unpackage/res/icons/80x80.png"
|
||||
},
|
||||
"iphone" : {
|
||||
"app@2x" : "unpackage/res/icons/120x120.png",
|
||||
"app@3x" : "unpackage/res/icons/180x180.png",
|
||||
"notification@2x" : "unpackage/res/icons/40x40.png",
|
||||
"notification@3x" : "unpackage/res/icons/60x60.png",
|
||||
"settings@2x" : "unpackage/res/icons/58x58.png",
|
||||
"settings@3x" : "unpackage/res/icons/87x87.png",
|
||||
"spotlight@2x" : "unpackage/res/icons/80x80.png",
|
||||
"spotlight@3x" : "unpackage/res/icons/120x120.png"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"nativePlugins" : {
|
||||
"LcPrinter" : {
|
||||
"__plugin_info__" : {
|
||||
"name" : "LcPrinter",
|
||||
"description" : "打印插件",
|
||||
"platforms" : "Android",
|
||||
"url" : "",
|
||||
"android_package_name" : "",
|
||||
"ios_bundle_id" : "",
|
||||
"isCloud" : false,
|
||||
"bought" : -1,
|
||||
"pid" : "",
|
||||
"parameters" : {}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
/* 快应用特有相关 */
|
||||
"quickapp" : {},
|
||||
/* 小程序特有相关 */
|
||||
"mp-weixin" : {
|
||||
"appid" : "",
|
||||
"setting" : {
|
||||
"urlCheck" : false
|
||||
},
|
||||
"usingComponents" : true
|
||||
},
|
||||
"mp-alipay" : {
|
||||
"usingComponents" : true
|
||||
},
|
||||
"mp-baidu" : {
|
||||
"usingComponents" : true
|
||||
},
|
||||
"mp-toutiao" : {
|
||||
"usingComponents" : true
|
||||
},
|
||||
"uniStatistics" : {
|
||||
"enable" : false
|
||||
},
|
||||
"vueVersion" : "2"
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
{
|
||||
"name": "LcPrinter",
|
||||
"id": "LcPrinter",
|
||||
"version": "1.0.1",
|
||||
"description": "打印插件",
|
||||
"_dp_type":"nativeplugin",
|
||||
"_dp_nativeplugin":{
|
||||
"android": {
|
||||
"plugins": [
|
||||
{
|
||||
"type": "module",
|
||||
"name": "LcPrinter",
|
||||
"class": "uni.dcloud.io.uniplugin_lcprint.PrinterModule"
|
||||
}
|
||||
],
|
||||
"abis": [
|
||||
"armeabi-v7a",
|
||||
"arm64-v8a",
|
||||
"x86"
|
||||
],
|
||||
"integrateType": "aar",
|
||||
"minSdkVersion" : 15
|
||||
}
|
||||
}
|
||||
}
|
5765
erp_mobile/package-lock.json
generated
|
@ -1,7 +0,0 @@
|
|||
{
|
||||
"dependencies": {
|
||||
"node-sass": "^7.0.1",
|
||||
"sass-loader": "^12.6.0",
|
||||
"vuex": "^4.0.2"
|
||||
}
|
||||
}
|
|
@ -1,88 +0,0 @@
|
|||
{
|
||||
"pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
|
||||
{
|
||||
"path": "pages/home/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "首页",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/login/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "登录",
|
||||
"enablePullDownRefresh": false,
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/productList/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "产品管理",
|
||||
"enablePullDownRefresh": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/productCreateForm/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "新增产品",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/productUpdateForm/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "编辑产品",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/batchReport/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "批次报表",
|
||||
"enablePullDownRefresh": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/stockInTask/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "入库任务",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/stockInForm/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "入库商品",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/stockOutTask/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "出库任务",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/stockOutForm/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "出库商品",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/stockCheckForm/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "盘点",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"globalStyle": {
|
||||
"navigationBarTextStyle": "white",
|
||||
"navigationBarTitleText": "uni-app",
|
||||
"navigationBarBackgroundColor": "#3c9cff",
|
||||
"backgroundColor": "#F8F8F8"
|
||||
}
|
||||
}
|
|
@ -1,123 +0,0 @@
|
|||
<template>
|
||||
<view>
|
||||
<view style="padding: 12rpx;">
|
||||
<u-search placeholder="请扫描商品条码" :focus="true" searchIcon="scan" v-model="searchForm.search" shape="square"
|
||||
:clearabled="true" :show-action="false" @search="searchData">
|
||||
</u-search>
|
||||
</view>
|
||||
|
||||
<view>
|
||||
<u-cell v-for="(item, index) in dataItems" :key="index">
|
||||
<view class="cell-title" slot="title">{{ item.goods_number }}</view>
|
||||
<view class="cell-label" slot="label">
|
||||
<u-row class="cell-label-row">
|
||||
<u-col span="6">商品名称: {{ item.goods_name }}</u-col>
|
||||
<u-col span="6">批次: {{ item.number }}</u-col>
|
||||
</u-row>
|
||||
<u-row class="cell-label-row">
|
||||
<u-col span="6">仓库: {{ item.warehouse_name }}</u-col>
|
||||
<u-col span="6">数量: {{ item.remain_quantity }}</u-col>
|
||||
</u-row>
|
||||
</view>
|
||||
</u-cell>
|
||||
</view>
|
||||
|
||||
<view style="padding: 12rpx 0;">
|
||||
<u-loadmore :status="loadMoreStatus" loading-text="正在加载中" loadmore-text="加载更多" nomore-text="没有更多了"
|
||||
@loadmore="loadMoreData" />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { batchReportList } from '@/api/report.js';
|
||||
import { mapState } from 'vuex';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
searchForm: { page: 1, page_size: 6, has_stock: true },
|
||||
dataLoading: false,
|
||||
dataItems: [],
|
||||
dataCount: 0,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
currentWarehouse: (state) => state.system.currentWarehouse,
|
||||
}),
|
||||
loadMoreStatus() {
|
||||
if (this.dataLoading) {
|
||||
return 'loading';
|
||||
}
|
||||
|
||||
if (this.dataItems.length == this.dataCount) {
|
||||
return 'nomore';
|
||||
}
|
||||
|
||||
return 'loadmore';
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
initData() {
|
||||
this.searchForm.warehouse = this.currentWarehouse;
|
||||
this.searchForm.page = 1;
|
||||
this.list();
|
||||
},
|
||||
list() {
|
||||
if (this.searchForm.page == 1) {
|
||||
uni.showLoading({ title: '加载中' });
|
||||
}
|
||||
|
||||
this.dataLoading = true;
|
||||
batchReportList(this.searchForm).then((data) => {
|
||||
console.log('-----------------------BatchReportStart-----------------------')
|
||||
this.dataCount = data.count;
|
||||
if (this.searchForm.page == 1) {
|
||||
this.dataItems = data.results;
|
||||
} else {
|
||||
this.dataItems = this.dataItems.concat(data.results);
|
||||
}
|
||||
}).finally(() => {
|
||||
console.log('-----------------------BatchReportEnd-----------------------')
|
||||
uni.stopPullDownRefresh();
|
||||
uni.hideLoading();
|
||||
this.dataLoading = false;
|
||||
});
|
||||
},
|
||||
loadMoreData() {
|
||||
if (this.dataItems.length < this.dataCount) {
|
||||
this.searchForm.page += 1;
|
||||
this.list();
|
||||
}
|
||||
},
|
||||
searchData() {
|
||||
this.searchForm.page = 1;
|
||||
this.list();
|
||||
},
|
||||
},
|
||||
onShow() {
|
||||
this.initData();
|
||||
},
|
||||
onPullDownRefresh() {
|
||||
this.searchData();
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.cell-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.cell-label {
|
||||
font-size: 24rpx;
|
||||
color: #0005;
|
||||
}
|
||||
|
||||
.cell-label-row {
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
</style>
|
|
@ -1,159 +0,0 @@
|
|||
<template>
|
||||
<view style="background-color: #f8f8f8; height: 100vh;">
|
||||
<view>
|
||||
<u-row style="background-color: #3c9cff; color: #ffffff; height: 72rpx; padding-right: 16rpx;">
|
||||
<u-col span="3" @click="showPicker = true" style="text-align: left; float: left;">
|
||||
<u-icon name="arrow-down" :label="currentWarehouseName" labelPos="left" color="#ffffff" labelColor="#ffffff"
|
||||
space="6px" style="font-weight: 600; text-align: left; float: left;"></u-icon>
|
||||
</u-col>
|
||||
<u-col offset="6" span="3" @click="showActionSheet = true">
|
||||
<u-icon name="arrow-down" :label="userItem.name" labelPos="left" color="#ffffff" labelColor="#ffffff"
|
||||
space="6px" style="font-weight: 600;"></u-icon>
|
||||
</u-col>
|
||||
</u-row>
|
||||
|
||||
<u-action-sheet :actions="actionList" :show="showActionSheet" :closeOnClickOverlay="true"
|
||||
@close="showActionSheet = false" @select="selectAction"></u-action-sheet>
|
||||
<u-picker :show="showPicker" :columns="[warehouseItems]" keyName="name" :closeOnClickOverlay="true"
|
||||
@close="showPicker = false" @cancel="showPicker = false" @confirm="changeCurrentWarehouse"></u-picker>
|
||||
</view>
|
||||
|
||||
<u-row style="background-color: #fff;">
|
||||
<u-col span="4">
|
||||
<view
|
||||
style="color: #666; text-align: center; border-bottom: 1px solid #8888; border-right: 1px solid #8888; padding: 20rpx 0;">
|
||||
<view>待入库</view>
|
||||
<view style="color: #398ade;">{{ item.stock_in_task_count }}</view>
|
||||
</view>
|
||||
</u-col>
|
||||
<u-col span="4">
|
||||
<view style="color: #666; text-align: center; border-bottom: 1px solid #8888; padding: 20rpx 0;">
|
||||
<view>待出库</view>
|
||||
<view style="color: #53c21d;">{{ item.stock_out_task_count }}</view>
|
||||
</view>
|
||||
</u-col>
|
||||
<u-col span="4">
|
||||
<view
|
||||
style="color: #666; text-align: center; border-bottom: 1px solid #8888; border-left: 1px solid #8888;padding: 20rpx 0;">
|
||||
<view>临期预警</view>
|
||||
<view style="color: #e45656;">{{ item.expiration_warning_count }}</view>
|
||||
</view>
|
||||
</u-col>
|
||||
</u-row>
|
||||
|
||||
<u-grid :border="false" @click="onClickGrid">
|
||||
<u-grid-item v-for="(item, index) in funcList" :key="index"
|
||||
style="padding: 36rpx 0; border-right:1px solid #8888; border-bottom: 1px solid #8888;">
|
||||
<image :src="item.image" style="width: 100rpx; height: 100rpx" />
|
||||
<view style="margin-top: 16rpx;">
|
||||
<text style="font-size: 28rpx;">{{item.title}}</text>
|
||||
</view>
|
||||
</u-grid-item>
|
||||
</u-grid>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getInfo, homeOverview } from 'api/system.js';
|
||||
import { warehouseOption } from 'api/option.js';
|
||||
import { mapState, mapMutations } from 'vuex';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
funcList: [
|
||||
{
|
||||
title: '产品管理',
|
||||
path: '/pages/productList/index',
|
||||
image: '../../static/product.png',
|
||||
},
|
||||
{
|
||||
title: '批次报表',
|
||||
path: '/pages/batchReport/index',
|
||||
image: '../../static/func6.png',
|
||||
},
|
||||
{
|
||||
title: '入库',
|
||||
path: '/pages/stockInTask/index',
|
||||
image: '../../static/func1.png',
|
||||
},
|
||||
{
|
||||
title: '出库',
|
||||
path: '/pages/stockOutTask/index',
|
||||
image: '../../static/func8.png',
|
||||
},
|
||||
{
|
||||
title: '盘点',
|
||||
path: '/pages/stockCheckForm/index',
|
||||
image: '../../static/func9.png',
|
||||
},
|
||||
],
|
||||
showActionSheet: false,
|
||||
showPicker: false,
|
||||
actionList: [
|
||||
{ code: 'logout', name: '退出登录' }
|
||||
],
|
||||
item: {},
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
userItem: (state) => state.system.userItem,
|
||||
warehouseItems: (state) => state.system.warehouseItems,
|
||||
currentWarehouse: (state) => state.system.currentWarehouse,
|
||||
}),
|
||||
currentWarehouseName() {
|
||||
for (let item of this.warehouseItems) {
|
||||
if (item.id == this.currentWarehouse) {
|
||||
return item.name;
|
||||
}
|
||||
}
|
||||
return '所有仓库';
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapMutations(['setUserItem', 'setWarehouseItems', 'setCurrentWarehouse']),
|
||||
initData() {
|
||||
getInfo().then((data) => {
|
||||
console.log(data)
|
||||
this.setUserItem(data);
|
||||
});
|
||||
|
||||
warehouseOption().then((data) => {
|
||||
let warehouseItems = [{ id: undefined, name: '所有仓库' }, ...data.results];
|
||||
this.setWarehouseItems(warehouseItems);
|
||||
if (!this.currentWarehouse) {
|
||||
this.setCurrentWarehouse(warehouseItems[0].id);
|
||||
}
|
||||
});
|
||||
|
||||
homeOverview().then((data) => {
|
||||
this.item = data;
|
||||
});
|
||||
},
|
||||
onClickGrid(index) {
|
||||
uni.navigateTo({ url: this.funcList[index].path });
|
||||
},
|
||||
changeCurrentWarehouse(item) {
|
||||
this.showPicker = false;
|
||||
this.setCurrentWarehouse(this.warehouseItems[item.indexs[0]].id);
|
||||
},
|
||||
selectAction(item) {
|
||||
this.showActionSheet = false;
|
||||
|
||||
if (item.code == 'logout') {
|
||||
uni.removeStorageSync('access');
|
||||
uni.removeStorageSync('refresh');
|
||||
uni.redirectTo({ url: '../login/index' });
|
||||
}
|
||||
},
|
||||
},
|
||||
onShow() {
|
||||
this.initData();
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
|
@ -1,98 +0,0 @@
|
|||
<template>
|
||||
<view style="text-align: center;">
|
||||
<!-- <view style="padding-top: 180rpx;">
|
||||
<image src="../../static/images/logo.png" style="width: 180rpx; height: 180rpx;"></image>
|
||||
</view>
|
||||
-->
|
||||
|
||||
<view style="padding-top: 180rpx;">
|
||||
<image src="../../static/images/logo.png" style="width: 160rpx; height: 160rpx;"></image>
|
||||
</view>
|
||||
|
||||
<view style="color: rgb(24, 144, 255); margin-top: 24rpx;">
|
||||
<view style="font-size: 52rpx; font-weight: 800;">Himool ERP</view>
|
||||
<view style="font-size: 30rpx; margin-top: 8rpx;">盒木进销存管理系统</view>
|
||||
</view>
|
||||
|
||||
<view style="width: 70%; margin: auto; padding-top: 10rpx;">
|
||||
<view class="form-input">
|
||||
<u--input v-model="form.number" placeholder="公司编号" border="none" prefixIcon="tags"
|
||||
prefixIconStyle="font-size: 24px; color: #3c9cffbb" fontSize="18px" clearable></u--input>
|
||||
</view>
|
||||
<view class="form-input">
|
||||
<u--input v-model="form.username" placeholder="用户名" border="none" prefixIcon="account"
|
||||
prefixIconStyle="font-size: 24px; color: #3c9cffbb" fontSize="18px" clearable></u--input>
|
||||
</view>
|
||||
<view class="form-input">
|
||||
<u--input v-model="form.password" placeholder="密码" border="none" prefixIcon="lock"
|
||||
prefixIconStyle="font-size: 24px; color: #3c9cffbb" fontSize="18px" clearable type="password"></u--input>
|
||||
</view>
|
||||
|
||||
<view style="padding-top: 80rpx;">
|
||||
<u-button type="primary" text="登录" size="large" :loading="loginLoading" :disabled="loginLoading"
|
||||
@click="handleLogin"></u-button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<u-toast ref="uToast"></u-toast>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getToken } from '@/api/system.js';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
loginLoading: false,
|
||||
form: {
|
||||
number: '',
|
||||
username: '',
|
||||
password: '',
|
||||
},
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
handleLogin() {
|
||||
if (!this.validateForm()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.loginLoading = true;
|
||||
getToken(this.form).then((data) => {
|
||||
uni.setStorageSync('access', data.access);
|
||||
uni.setStorageSync('refresh', data.refresh);
|
||||
uni.redirectTo({ url: '../home/index' });
|
||||
}).finally(() => {
|
||||
this.loginLoading = false;
|
||||
})
|
||||
},
|
||||
validateForm() {
|
||||
if (!this.form.number) {
|
||||
this.$refs.uToast.show({ type: 'warning', duration: 1000, message: '请输入公司编号' })
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.form.username) {
|
||||
this.$refs.uToast.show({ type: 'warning', duration: 1000, message: '请输入用户名' })
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.form.password) {
|
||||
this.$refs.uToast.show({ type: 'warning', duration: 1000, message: '请输入密码' })
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.form-input {
|
||||
border-bottom: 1px solid #3c9cffbb;
|
||||
padding: 16rpx 8rpx;
|
||||
margin-top: 16rpx;
|
||||
}
|
||||
</style>
|
|
@ -1,174 +0,0 @@
|
|||
<template>
|
||||
<view>
|
||||
<view style="padding: 0 24rpx; margin-bottom: 100rpx;">
|
||||
<u--form labelWidth="100">
|
||||
<u-form-item label="编号:" required borderBottom>
|
||||
<u--input v-model="dataForm.number" border="none" />
|
||||
</u-form-item>
|
||||
<u-form-item label="名称:" required borderBottom>
|
||||
<u--input v-model="dataForm.name" border="none" />
|
||||
</u-form-item>
|
||||
<u-form-item label="条码:" borderBottom>
|
||||
<u--input v-model="dataForm.barcode" border="none" />
|
||||
</u-form-item>
|
||||
</u-form-item>
|
||||
<u-form-item label="分类:" borderBottom @click="showCategoryPicker = true">
|
||||
<u--input :value="dataForm.category_name" disabled disabledColor="#ffffff" border="none" />
|
||||
</u-form-item>
|
||||
<u-form-item label="单位:" borderBottom @click="showUnitPicker = true">
|
||||
<u--input :value="dataForm.unit_name" disabled disabledColor="#ffffff" border="none" />
|
||||
</u-form-item>
|
||||
<u-form-item label="规格:" borderBottom>
|
||||
<u--input v-model="dataForm.spec" border="none" />
|
||||
</u-form-item>
|
||||
|
||||
<u-form-item label="采购价:" borderBottom>
|
||||
<u--input v-model="dataForm.purchase_price" type="number" border="none" />
|
||||
</u-form-item>
|
||||
<u-form-item label="零售价:" borderBottom>
|
||||
<u--input v-model="dataForm.retail_price" type="number" border="none" />
|
||||
</u-form-item>
|
||||
<u-form-item label="等级价一:" borderBottom>
|
||||
<u--input v-model="dataForm.level_price1" type="number" border="none" />
|
||||
</u-form-item>
|
||||
<u-form-item label="等级价二:" borderBottom>
|
||||
<u--input v-model="dataForm.level_price2" type="number" border="none" />
|
||||
</u-form-item>
|
||||
<u-form-item label="等级价三:" borderBottom>
|
||||
<u--input v-model="dataForm.level_price3" type="number" border="none" />
|
||||
</u-form-item>
|
||||
|
||||
<u-form-item label="启用批次控制:" borderBottom>
|
||||
<u-switch v-model="dataForm.enable_batch_control" />
|
||||
</u-form-item>
|
||||
<u-form-item v-if="dataForm.enable_batch_control" label="保质期(天):" borderBottom>
|
||||
<u--input v-model="dataForm.shelf_life_days" border="none" />
|
||||
</u-form-item>
|
||||
<u-form-item v-if="dataForm.enable_batch_control" label="预警天数:" borderBottom>
|
||||
<u--input v-model="dataForm.shelf_life_warning_days" border="none" />
|
||||
</u-form-item>
|
||||
<u-form-item label="库存预警:" borderBottom>
|
||||
<u-switch v-model="dataForm.enable_inventory_warning" />
|
||||
</u-form-item>
|
||||
<u-form-item v-if="dataForm.enable_inventory_warning" label="库存上限:" borderBottom>
|
||||
<u--input v-model="dataForm.inventory_upper" border="none" />
|
||||
</u-form-item>
|
||||
<u-form-item v-if="dataForm.enable_inventory_warning" label="库存下限:" borderBottom>
|
||||
<u--input v-model="dataForm.inventory_lower" border="none" />
|
||||
</u-form-item>
|
||||
<u-form-item label="状态:" borderBottom>
|
||||
<u-switch v-model="dataForm.is_active" />
|
||||
</u-form-item>
|
||||
<u-form-item label="备注:" borderBottom>
|
||||
<u--input v-model="dataForm.remark" border="none" />
|
||||
</u-form-item>
|
||||
</u--form>
|
||||
</view>
|
||||
|
||||
<view style="position: fixed; bottom: 0; width: 100%;">
|
||||
<u-button type="primary" size="large" text="创建" @click="create"></u-button>
|
||||
</view>
|
||||
|
||||
<u-toast ref="uToast"></u-toast>
|
||||
<u-picker :show="showCategoryPicker" :columns="[categoryItems]" keyName="name" :closeOnClickOverlay="true"
|
||||
@close="showCategoryPicker = false" @cancel="showCategoryPicker = false" @confirm="changeCategory" />
|
||||
<u-picker :show="showUnitPicker" :columns="[unitItems]" keyName="name" :closeOnClickOverlay="true"
|
||||
@close="showUnitPicker = false" @cancel="showUnitPicker = false" @confirm="changeUnit" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { productCreate, productNumber } from '@/api/product.js';
|
||||
import { categoryOption, unitOption } from '@/api/option.js';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
dataForm: {
|
||||
number: '',
|
||||
name: '',
|
||||
barcode: '',
|
||||
unit: null,
|
||||
unit_name: '',
|
||||
category: null,
|
||||
category_name: '',
|
||||
spec: '',
|
||||
|
||||
length: null,
|
||||
width: null,
|
||||
height: null,
|
||||
volume: null,
|
||||
weight: null,
|
||||
spec: '',
|
||||
unit: '',
|
||||
enable_shelf_life: false,
|
||||
shelf_life_days: null,
|
||||
shelf_life_warning_days: 0,
|
||||
enable_inventory_warning: false,
|
||||
inventory_upper: null,
|
||||
inventory_lower: null,
|
||||
is_active: true,
|
||||
remark: '',
|
||||
},
|
||||
showUnitPicker: false,
|
||||
showCategoryPicker: false,
|
||||
unitItems: [],
|
||||
categoryItems: [],
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
create() {
|
||||
if (!this.dataForm.number) {
|
||||
this.$refs.uToast.show({ message: '请填写编号' });
|
||||
return
|
||||
}
|
||||
|
||||
if (!this.dataForm.name) {
|
||||
this.$refs.uToast.show({ message: '请填写名称' });
|
||||
return
|
||||
}
|
||||
|
||||
productCreate(this.dataForm).then((data) => {
|
||||
uni.showToast({ title: '创建成功', duration: 2000 });
|
||||
uni.navigateBack();
|
||||
this.eventChannel.emit('onCreate', data);
|
||||
});
|
||||
},
|
||||
changeUnit(item) {
|
||||
this.showUnitPicker = false;
|
||||
this.dataForm.unit = this.unitItems[item.indexs[0]].id;
|
||||
this.dataForm.unit_name = this.unitItems[item.indexs[0]].name;
|
||||
},
|
||||
changeCategory(item) {
|
||||
this.showCategoryPicker = false;
|
||||
this.dataForm.category = this.categoryItems[item.indexs[0]].id;
|
||||
this.dataForm.category_name = this.categoryItems[item.indexs[0]].name;
|
||||
},
|
||||
},
|
||||
onShow() {
|
||||
if (!this.dataForm.number || this.dataForm.number.length === 0) {
|
||||
productNumber().then((data) => {
|
||||
this.dataForm.number = data.number;
|
||||
});
|
||||
}
|
||||
|
||||
unitOption({ page: 1, page_size: 999999 }).then((data) => {
|
||||
this.unitItems = data.results;
|
||||
});
|
||||
|
||||
categoryOption({ page: 1, page_size: 999999 }).then((data) => {
|
||||
this.categoryItems = data.results;
|
||||
});
|
||||
},
|
||||
onLoad(option) {
|
||||
this.eventChannel = this.getOpenerEventChannel();
|
||||
if (option.number) {
|
||||
this.dataForm.number = option.number;
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -1,190 +0,0 @@
|
|||
<template>
|
||||
<view style="margin-bottom: 100rpx;">
|
||||
<view style="padding: 12rpx;">
|
||||
<u-search placeholder="请扫描物料条码" :focus="true" searchIcon="scan" v-model="searchForm.search" shape="square"
|
||||
:clearabled="true" :show-action="false" @search="searchData">
|
||||
</u-search>
|
||||
</view>
|
||||
|
||||
<view>
|
||||
<u-cell v-for="(item, index) in dataItems" :key="index">
|
||||
<view class="cell-title" slot="title">{{ item.name }}</view>
|
||||
<view class="cell-label" slot="label">
|
||||
<u-row class="cell-label-row">
|
||||
<u-col span="6">编号: {{ item.number }}</u-col>
|
||||
<u-col span="6">分类: {{ item.category_name }}</u-col>
|
||||
</u-row>
|
||||
<u-row class="cell-label-row">
|
||||
<u-col span="6">规格: {{ item.spec }}</u-col>
|
||||
<u-col span="6">备注: {{ item.remark }}</u-col>
|
||||
</u-row>
|
||||
|
||||
<u-row gutter="16" style="margin-top: 24rpx;">
|
||||
<u-col span="4">
|
||||
<u-button type="primary" text="编辑" @click="toUpdateForm(item)"></u-button>
|
||||
</u-col>
|
||||
<u-col span="4">
|
||||
<u-button type="error" text="删除" @click="openDestroyModal(item)"></u-button>
|
||||
</u-col>
|
||||
<u-col span="4">
|
||||
<u-button type="success" text="打印" @click="openPrintModal(item)"></u-button>
|
||||
</u-col>
|
||||
</u-row>
|
||||
</view>
|
||||
</u-cell>
|
||||
</view>
|
||||
|
||||
<view style="position: fixed; bottom: 0; width: 100%;">
|
||||
<u-button type="primary" size="large" text="新增物料" @click="toCreateForm">
|
||||
</u-button>
|
||||
</view>
|
||||
|
||||
<view style="padding: 12rpx 0;">
|
||||
<u-loadmore :status="loadMoreStatus" loading-text="正在加载中" loadmore-text="加载更多" nomore-text="没有更多了"
|
||||
@loadmore="loadMoreData" />
|
||||
</view>
|
||||
|
||||
<u-modal :show="showDestroyModal" title="确定删除吗?" :showCancelButton="true" @cancel="showDestroyModal = false"
|
||||
@confirm="destroy" />
|
||||
<print-material-modal :show="showPrintModal" :number="printItem.number" :name="printItem.name"
|
||||
@cancel="showPrintModal = false" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import PrintMaterialModal from '@/components/PrintMaterialModal/index';
|
||||
import { productList, productDestroy } from '@/api/product.js';
|
||||
import { mapState } from 'vuex';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
PrintMaterialModal,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
searchForm: { page: 1, page_size: 8 },
|
||||
dataLoading: false,
|
||||
dataItems: [],
|
||||
dataCount: 0,
|
||||
|
||||
showPrintModal: false,
|
||||
printItem: {},
|
||||
|
||||
showDestroyModal: false,
|
||||
destroyItem: {},
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
currentWarehouse: (state) => state.system.currentWarehouse,
|
||||
}),
|
||||
loadMoreStatus() {
|
||||
if (this.dataLoading) {
|
||||
return 'loading';
|
||||
}
|
||||
|
||||
if (this.dataItems.length == this.dataCount) {
|
||||
return 'nomore';
|
||||
}
|
||||
|
||||
return 'loadmore';
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
initData() {
|
||||
this.searchForm.page = 1;
|
||||
this.list();
|
||||
},
|
||||
list() {
|
||||
if (this.searchForm.page == 1) {
|
||||
uni.showLoading({ title: '加载中' });
|
||||
}
|
||||
|
||||
this.dataLoading = true;
|
||||
productList(this.searchForm).then((data) => {
|
||||
this.dataCount = data.count;
|
||||
if (this.searchForm.page == 1) {
|
||||
this.dataItems = data.results;
|
||||
} else {
|
||||
this.dataItems = this.dataItems.concat(data.results);
|
||||
}
|
||||
}).finally(() => {
|
||||
uni.stopPullDownRefresh();
|
||||
uni.hideLoading();
|
||||
this.dataLoading = false;
|
||||
});
|
||||
},
|
||||
destroy() {
|
||||
productDestroy({ id: this.destroyItem.id }).then(() => {
|
||||
this.dataItems.splice(this.dataItems.findIndex(item => item.id == this.destroyItem.id), 1);
|
||||
uni.showToast({ title: '删除成功', duration: 2000 });
|
||||
this.showDestroyModal = false;
|
||||
});
|
||||
},
|
||||
loadMoreData() {
|
||||
if (this.dataItems.length < this.dataCount) {
|
||||
this.searchForm.page += 1;
|
||||
this.list();
|
||||
}
|
||||
},
|
||||
searchData() {
|
||||
this.searchForm.page = 1;
|
||||
this.list();
|
||||
},
|
||||
openDestroyModal(item) {
|
||||
this.destroyItem = { ...item };
|
||||
this.showDestroyModal = true;
|
||||
},
|
||||
openPrintModal(item) {
|
||||
this.printItem = item;
|
||||
this.showPrintModal = true;
|
||||
},
|
||||
toCreateForm() {
|
||||
uni.navigateTo({
|
||||
url: '../productCreateForm/index',
|
||||
events: {
|
||||
onCreate(item) {
|
||||
this.dataItems.splice(0, 0, item);
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
toUpdateForm(item) {
|
||||
uni.navigateTo({
|
||||
url: '../productUpdateForm/index?item=' + encodeURIComponent(JSON.stringify(item)),
|
||||
events: {
|
||||
onUpdate(item) {
|
||||
let index = this.dataItems.findIndex((dataItem) => dataItem.id === item.id);
|
||||
if (index !== -1) {
|
||||
this.dataItems.splice(index, 1, item);
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
onShow() {
|
||||
this.initData();
|
||||
},
|
||||
onPullDownRefresh() {
|
||||
this.searchData();
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.cell-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.cell-label {
|
||||
font-size: 24rpx;
|
||||
color: #0005;
|
||||
}
|
||||
|
||||
.cell-label-row {
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
</style>
|
|
@ -1,166 +0,0 @@
|
|||
<template>
|
||||
<view>
|
||||
<view style="padding: 0 24rpx; margin-bottom: 100rpx;">
|
||||
<u--form labelWidth="100">
|
||||
<u-form-item label="编号:" required borderBottom>
|
||||
<u--input v-model="dataForm.number" border="none" />
|
||||
</u-form-item>
|
||||
<u-form-item label="名称:" required borderBottom>
|
||||
<u--input v-model="dataForm.name" border="none" />
|
||||
</u-form-item>
|
||||
<u-form-item label="条码:" borderBottom>
|
||||
<u--input v-model="dataForm.barcode" border="none" />
|
||||
</u-form-item>
|
||||
</u-form-item>
|
||||
<u-form-item label="分类:" borderBottom @click="showCategoryPicker = true">
|
||||
<u--input :value="dataForm.category_name" disabled disabledColor="#ffffff" border="none" />
|
||||
</u-form-item>
|
||||
<u-form-item label="单位:" borderBottom @click="showUnitPicker = true">
|
||||
<u--input :value="dataForm.unit_name" disabled disabledColor="#ffffff" border="none" />
|
||||
</u-form-item>
|
||||
<u-form-item label="规格:" borderBottom>
|
||||
<u--input v-model="dataForm.spec" border="none" />
|
||||
</u-form-item>
|
||||
|
||||
<u-form-item label="采购价:" borderBottom>
|
||||
<u--input v-model="dataForm.purchase_price" type="number" border="none" />
|
||||
</u-form-item>
|
||||
<u-form-item label="零售价:" borderBottom>
|
||||
<u--input v-model="dataForm.retail_price" type="number" border="none" />
|
||||
</u-form-item>
|
||||
<u-form-item label="等级价一:" borderBottom>
|
||||
<u--input v-model="dataForm.level_price1" type="number" border="none" />
|
||||
</u-form-item>
|
||||
<u-form-item label="等级价二:" borderBottom>
|
||||
<u--input v-model="dataForm.level_price2" type="number" border="none" />
|
||||
</u-form-item>
|
||||
<u-form-item label="等级价三:" borderBottom>
|
||||
<u--input v-model="dataForm.level_price3" type="number" border="none" />
|
||||
</u-form-item>
|
||||
|
||||
<u-form-item label="启用批次控制:" borderBottom>
|
||||
<u-switch v-model="dataForm.enable_batch_control" />
|
||||
</u-form-item>
|
||||
<u-form-item v-if="dataForm.enable_batch_control" label="保质期(天):" borderBottom>
|
||||
<u--input v-model="dataForm.shelf_life_days" border="none" />
|
||||
</u-form-item>
|
||||
<u-form-item v-if="dataForm.enable_batch_control" label="预警天数:" borderBottom>
|
||||
<u--input v-model="dataForm.shelf_life_warning_days" border="none" />
|
||||
</u-form-item>
|
||||
<u-form-item label="库存预警:" borderBottom>
|
||||
<u-switch v-model="dataForm.enable_inventory_warning" />
|
||||
</u-form-item>
|
||||
<u-form-item v-if="dataForm.enable_inventory_warning" label="库存上限:" borderBottom>
|
||||
<u--input v-model="dataForm.inventory_upper" border="none" />
|
||||
</u-form-item>
|
||||
<u-form-item v-if="dataForm.enable_inventory_warning" label="库存下限:" borderBottom>
|
||||
<u--input v-model="dataForm.inventory_lower" border="none" />
|
||||
</u-form-item>
|
||||
<u-form-item label="状态:" borderBottom>
|
||||
<u-switch v-model="dataForm.is_active" />
|
||||
</u-form-item>
|
||||
<u-form-item label="备注:" borderBottom>
|
||||
<u--input v-model="dataForm.remark" border="none" />
|
||||
</u-form-item>
|
||||
</u--form>
|
||||
</view>
|
||||
|
||||
<view style="position: fixed; bottom: 0; width: 100%;">
|
||||
<u-button type="primary" size="large" text="保存" @click="update"></u-button>
|
||||
</view>
|
||||
|
||||
<u-toast ref="uToast"></u-toast>
|
||||
<u-picker :show="showCategoryPicker" :columns="[categoryItems]" keyName="name" :closeOnClickOverlay="true"
|
||||
@close="showCategoryPicker = false" @cancel="showCategoryPicker = false" @confirm="changeCategory" />
|
||||
<u-picker :show="showUnitPicker" :columns="[unitItems]" keyName="name" :closeOnClickOverlay="true"
|
||||
@close="showUnitPicker = false" @cancel="showUnitPicker = false" @confirm="changeUnit" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { productUpdate } from '@/api/product.js';
|
||||
import { categoryOption, unitOption } from '@/api/option.js';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
dataForm: {
|
||||
number: '',
|
||||
name: '',
|
||||
barcode: '',
|
||||
unit: null,
|
||||
unit_name: '',
|
||||
category: null,
|
||||
category_name: '',
|
||||
spec: '',
|
||||
|
||||
length: null,
|
||||
width: null,
|
||||
height: null,
|
||||
volume: null,
|
||||
weight: null,
|
||||
spec: '',
|
||||
unit: '',
|
||||
enable_shelf_life: false,
|
||||
shelf_life_days: null,
|
||||
shelf_life_warning_days: 0,
|
||||
enable_inventory_warning: false,
|
||||
inventory_upper: null,
|
||||
inventory_lower: null,
|
||||
is_active: true,
|
||||
remark: '',
|
||||
},
|
||||
showUnitPicker: false,
|
||||
showCategoryPicker: false,
|
||||
unitItems: [],
|
||||
categoryItems: [],
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
update() {
|
||||
if (!this.dataForm.number) {
|
||||
this.$refs.uToast.show({ message: '请填写编号' });
|
||||
return
|
||||
}
|
||||
|
||||
if (!this.dataForm.name) {
|
||||
this.$refs.uToast.show({ message: '请填写名称' });
|
||||
return
|
||||
}
|
||||
|
||||
productUpdate(this.dataForm).then((data) => {
|
||||
uni.showToast({ title: '保存成功', duration: 2000 });
|
||||
uni.navigateBack();
|
||||
this.eventChannel.emit('onUpdate', data);
|
||||
});
|
||||
},
|
||||
changeUnit(item) {
|
||||
this.showUnitPicker = false;
|
||||
this.dataForm.unit = this.unitItems[item.indexs[0]].id;
|
||||
this.dataForm.unit_name = this.unitItems[item.indexs[0]].name;
|
||||
},
|
||||
changeCategory(item) {
|
||||
this.showCategoryPicker = false;
|
||||
this.dataForm.category = this.categoryItems[item.indexs[0]].id;
|
||||
this.dataForm.category_name = this.categoryItems[item.indexs[0]].name;
|
||||
},
|
||||
},
|
||||
onShow() {
|
||||
unitOption({ page: 1, page_size: 999999 }).then((data) => {
|
||||
this.unitItems = data.results;
|
||||
});
|
||||
|
||||
categoryOption({ page: 1, page_size: 999999 }).then((data) => {
|
||||
this.categoryItems = data.results;
|
||||
});
|
||||
},
|
||||
onLoad(option) {
|
||||
this.eventChannel = this.getOpenerEventChannel();
|
||||
this.dataForm = { ...JSON.parse(decodeURIComponent(option.item)) };
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -1,344 +0,0 @@
|
|||
<template>
|
||||
<view>
|
||||
<view style="padding: 0 24rpx; margin-bottom: 100rpx;">
|
||||
<u--form labelWidth="75">
|
||||
<u-form-item label="盘点单号:" borderBottom>
|
||||
<u--input v-model="form.number" border="none"></u--input>
|
||||
</u-form-item>
|
||||
<u-form-item label="仓库:" borderBottom @click="showWarehousePicker = true">
|
||||
<u--input :value="form.warehouse_name" border="none" :disabled="true"></u--input>
|
||||
</u-form-item>
|
||||
<u-form-item label="经手人:" borderBottom @click="showUserPicker = true">
|
||||
<u--input :value="form.handler_name" border="none" :disabled="true"></u--input>
|
||||
</u-form-item>
|
||||
<u-form-item label="处理日期:" borderBottom @click="showDatePicker = true">
|
||||
<u--input :value="form.handle_time" border="none" :disabled="true"></u--input>
|
||||
</u-form-item>
|
||||
<u-form-item label="备注:" borderBottom>
|
||||
<u--input v-model="form.remark" border="none"></u--input>
|
||||
</u-form-item>
|
||||
<u-form-item label="产品编号:" borderBottom>
|
||||
<u--input border="none" :focus="inputFocus1" @focus="inputFocus1 = true" @blur="inputFocus1 = false"
|
||||
suffixIcon="scan" selectionStart="0" selectionEnd="999" placeholder="请扫描产品条形码" @confirm="scanGoods">
|
||||
</u--input>
|
||||
</u-form-item>
|
||||
</u--form>
|
||||
|
||||
<u-cell v-for="(item, index) in stockCheckGoodsItems">
|
||||
<view class="cell-title" slot="title">{{ item.goods_number }}</view>
|
||||
<view class="cell-label" slot="label">
|
||||
<u-row class="cell-label-row">
|
||||
<u-col span="6">产品名称: {{ item.goods_name }}</u-col>
|
||||
<u-col span="6">产品条码: {{ item.goods_barcode }}</u-col>
|
||||
</u-row>
|
||||
|
||||
<view v-if="item.enable_batch_control">
|
||||
<view v-for="_item in item.stock_check_batch_items" style="border: 1px solid #999; margin-top: 8rpx;">
|
||||
<u-row class="cell-label-row">
|
||||
<u-col span="6">批次编号: {{ _item.batch_number }}</u-col>
|
||||
</u-row>
|
||||
<u-row class="cell-label-row">
|
||||
<u-col span="6">账面数量: {{ _item.book_quantity }}</u-col>
|
||||
<u-col span="6">实际数量: {{ _item.actual_quantity }}</u-col>
|
||||
</u-row>
|
||||
</view>
|
||||
</view>
|
||||
<view v-else>
|
||||
<u-row class="cell-label-row">
|
||||
<u-col span="6">账面数量: {{ item.book_quantity }}</u-col>
|
||||
<u-col span="6">实际数量: {{ item.actual_quantity }}</u-col>
|
||||
</u-row>
|
||||
</view>
|
||||
</view>
|
||||
</u-cell>
|
||||
</view>
|
||||
|
||||
<view style="position: fixed; bottom: 0; width: 100%;">
|
||||
<u-button type="primary" size="large" text="确认盘点" :disabled="createLoading" :loading="createLoading"
|
||||
@click="create">
|
||||
</u-button>
|
||||
</view>
|
||||
|
||||
<u-datetime-picker :show="showDatePicker" mode="date" :closeOnClickOverlay="true" @close="showDatePicker = false"
|
||||
@cancel="showDatePicker = false" @confirm="selectDatePicker">
|
||||
</u-datetime-picker>
|
||||
<u-picker :show="showWarehousePicker" :columns="[warehouseItems]" keyName="name" :closeOnClickOverlay="true"
|
||||
@close="showWarehousePicker = false" @cancel="showWarehousePicker = false" @confirm="selectWarehouse"></u-picker>
|
||||
<u-picker :show="showUserPicker" :columns="[userItems]" keyName="name" :closeOnClickOverlay="true"
|
||||
@close="showUserPicker = false" @cancel="showUserPicker = false" @confirm="selectUser"></u-picker>
|
||||
<u-picker :show="showBatchPicker" :columns="[batchItems]" keyName="number" :closeOnClickOverlay="true"
|
||||
@close="showBatchPicker = false" @cancel="showBatchPicker = false" @confirm="selectBatch"></u-picker>
|
||||
|
||||
<u-toast ref="uToast"></u-toast>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { userOption, warehouseOption, batchOption, inventoryOption } from '@/api/option.js';
|
||||
import { stockCheckNumber, stockCheckCreate } from '@/api/stockCheck.js';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
eventChannel: null,
|
||||
showWarehousePicker: false,
|
||||
showUserPicker: false,
|
||||
showDatePicker: false,
|
||||
showBatchPicker: false,
|
||||
inputFocus1: false,
|
||||
|
||||
warehouseItems: [],
|
||||
userItems: [],
|
||||
form: {},
|
||||
stockCheckGoodsItems: [],
|
||||
batchItems: [],
|
||||
goodsItem: {},
|
||||
createLoading: false,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
initData() {
|
||||
warehouseOption({ page_size: 999999, is_active: true }).then(data => {
|
||||
this.warehouseItems = data.results;
|
||||
});
|
||||
userOption({ page_size: 999999, is_active: true }).then(data => {
|
||||
this.userItems = data.results;
|
||||
});
|
||||
stockCheckNumber().then(data => {
|
||||
this.form.number = data.number;
|
||||
});
|
||||
},
|
||||
validateGoods(value) {
|
||||
if (this.stockOutGoodsItem.goods_number == value) {
|
||||
this.stockOutGoodsItem.stockOutQuantity += 1;
|
||||
this.$refs.uToast.show({ message: '扫码成功' });
|
||||
} else {
|
||||
this.$refs.uToast.show({ message: '产品错误' });
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
this.inputFocus1 = true;
|
||||
}, 500);
|
||||
},
|
||||
confirm() {
|
||||
if (this.stockOutGoodsItem.enable_batch_control && !this.stockOutGoodsItem.batch) {
|
||||
this.$refs.uToast.show({ message: '请选择批次' });
|
||||
return;
|
||||
}
|
||||
|
||||
this.stockOutGoodsItem.isConfirmed = true;
|
||||
this.eventChannel.emit('confirm', this.stockOutGoodsItem);
|
||||
uni.navigateBack();
|
||||
},
|
||||
selectWarehouse(item) {
|
||||
let index = item.indexs[0];
|
||||
let warehouseItem = this.warehouseItems[index];
|
||||
this.form.warehouse = warehouseItem.id;
|
||||
this.form.warehouse_name = warehouseItem.name;
|
||||
this.showWarehousePicker = false;
|
||||
},
|
||||
selectUser(item) {
|
||||
let index = item.indexs[0];
|
||||
let userItem = this.userItems[index];
|
||||
this.form.handler = userItem.id;
|
||||
this.form.handler_name = userItem.name;
|
||||
this.showUserPicker = false;
|
||||
},
|
||||
selectDatePicker(item) {
|
||||
this.form.handle_time = this.formatDate(item.value);
|
||||
this.showDatePicker = false;
|
||||
},
|
||||
scanGoods(value) {
|
||||
if (!this.form.warehouse) {
|
||||
this.$refs.uToast.show({ message: `请选择仓库` });
|
||||
return;
|
||||
}
|
||||
|
||||
for (let stockCheckGoodsItem of this.stockCheckGoodsItems) {
|
||||
if (value == stockCheckGoodsItem.goods_number) {
|
||||
if (stockCheckGoodsItem.enable_batch_control) {
|
||||
this.batchItems = stockCheckGoodsItem.batchItems;
|
||||
if (this.batchItems.length == 1) {
|
||||
let stockCheckBatchItem = stockCheckGoodsItem.stock_check_batch_items[0];
|
||||
stockCheckBatchItem.actual_quantity += 1;
|
||||
stockCheckGoodsItem.actual_quantity += 1;
|
||||
} else {
|
||||
this.showBatchPicker = true;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
stockCheckGoodsItem.actual_quantity += 1;
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
this.inputFocus1 = true;
|
||||
}, 500);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let searchForm = {
|
||||
goods_number: value,
|
||||
page: 1,
|
||||
page_size: 6,
|
||||
is_active: true,
|
||||
warehouse: this.form.warehouse,
|
||||
};
|
||||
|
||||
inventoryOption(searchForm).then((data) => {
|
||||
if (data.results.length == 1) {
|
||||
let goodsItem = data.results[0];
|
||||
this.goodsItem = goodsItem;
|
||||
if (goodsItem.enable_batch_control) {
|
||||
this.getBatch(goodsItem);
|
||||
return;
|
||||
} else {
|
||||
this.stockCheckGoodsItems.push({
|
||||
goods: goodsItem.goods,
|
||||
goods_number: goodsItem.goods_number,
|
||||
goods_name: goodsItem.goods_name,
|
||||
goods_barcode: goodsItem.goods_barcode,
|
||||
enable_batch_control: goodsItem.enable_batch_control,
|
||||
book_quantity: goodsItem.total_quantity,
|
||||
actual_quantity: 1,
|
||||
stock_check_batch_items: [],
|
||||
});
|
||||
setTimeout(() => {
|
||||
this.inputFocus1 = true;
|
||||
}, 500);
|
||||
}
|
||||
} else {
|
||||
this.$refs.uToast.show({ message: `产品不存在或库存为零` });
|
||||
}
|
||||
});
|
||||
},
|
||||
getBatch(goodsItem) {
|
||||
let searchForm = {
|
||||
page: 1,
|
||||
page_size: 999999,
|
||||
has_stock: true,
|
||||
warehouse: this.form.warehouse,
|
||||
goods: goodsItem.goods,
|
||||
};
|
||||
|
||||
batchOption(searchForm).then((data) => {
|
||||
this.batchItems = data.results;
|
||||
if (this.batchItems.length == 0) {
|
||||
this.$refs.uToast.show({ message: `产品不存在或库存为零` });
|
||||
} else {
|
||||
if (this.batchItems.length == 1) {
|
||||
let batchItem = this.batchItems[0];
|
||||
this.stockCheckGoodsItems.push({
|
||||
goods: goodsItem.goods,
|
||||
goods_number: goodsItem.goods_number,
|
||||
goods_name: goodsItem.goods_name,
|
||||
goods_barcode: goodsItem.goods_barcode,
|
||||
enable_batch_control: goodsItem.enable_batch_control,
|
||||
book_quantity: goodsItem.total_quantity,
|
||||
actual_quantity: 1,
|
||||
stock_check_batch_items: [{
|
||||
id: batchItem.id,
|
||||
batch_number: batchItem.number,
|
||||
book_quantity: batchItem.remain_quantity,
|
||||
actual_quantity: 1,
|
||||
}],
|
||||
batchItems: [...this.batchItems],
|
||||
});
|
||||
} else {
|
||||
this.showBatchPicker = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
selectBatch(item) {
|
||||
this.showBatchPicker = false;
|
||||
let batchItem = this.batchItems[item.indexs[0]];
|
||||
let index = this.stockCheckGoodsItems.findIndex((item) => item.goods == batchItem.goods);
|
||||
if (index == -1) {
|
||||
this.stockCheckGoodsItems.push({
|
||||
goods: this.goodsItem.goods,
|
||||
goods_number: this.goodsItem.goods_number,
|
||||
goods_name: this.goodsItem.goods_name,
|
||||
goods_barcode: this.goodsItem.goods_barcode,
|
||||
enable_batch_control: this.goodsItem.enable_batch_control,
|
||||
book_quantity: this.goodsItem.total_quantity,
|
||||
actual_quantity: 1,
|
||||
stock_check_batch_items: [{
|
||||
id: batchItem.id,
|
||||
batch_number: batchItem.number,
|
||||
book_quantity: batchItem.remain_quantity,
|
||||
actual_quantity: 1,
|
||||
}],
|
||||
batchItems: [...this.batchItems],
|
||||
});
|
||||
} else {
|
||||
let goodsItem = this.stockCheckGoodsItems[index];
|
||||
goodsItem.actual_quantity += 1;
|
||||
let batchIndex = goodsItem.stock_check_batch_items.findIndex((item) => item.id == batchItem.id);
|
||||
if (batchIndex == -1) {
|
||||
goodsItem.stock_check_batch_items.push({
|
||||
id: batchItem.id,
|
||||
batch_number: batchItem.number,
|
||||
book_quantity: batchItem.remain_quantity,
|
||||
actual_quantity: 1,
|
||||
});
|
||||
} else {
|
||||
let stockCheckBatchItem = goodsItem.stock_check_batch_items[batchIndex];
|
||||
}
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
this.inputFocus1 = true;
|
||||
}, 500);
|
||||
},
|
||||
formatDate(datetime) {
|
||||
let date = new Date(datetime);
|
||||
let year = date.getFullYear(),
|
||||
month = ("0" + (date.getMonth() + 1)).slice(-2),
|
||||
sdate = ("0" + date.getDate()).slice(-2);
|
||||
return year + "-" + month + "-" + sdate;
|
||||
},
|
||||
create() {
|
||||
if (!this.form.handler) {
|
||||
this.$refs.uToast.show({ message: '请选择经手人' });
|
||||
return
|
||||
}
|
||||
|
||||
if (!this.form.handle_time) {
|
||||
this.$refs.uToast.show({ message: '请选择处理日期' });
|
||||
return
|
||||
}
|
||||
|
||||
if (this.stockCheckGoodsItems.length == 0) {
|
||||
this.$refs.uToast.show({ message: '请盘点物料' });
|
||||
return
|
||||
}
|
||||
|
||||
let createForm = {
|
||||
...this.form,
|
||||
stock_check_goods_Items: this.stockCheckGoodsItems,
|
||||
};
|
||||
|
||||
console.log('===', createForm)
|
||||
|
||||
uni.showLoading({ title: '创建中' });
|
||||
this.createLoading = true;
|
||||
stockCheckCreate(createForm).then(() => {
|
||||
uni.redirectTo({ url: '../stockCheckForm/index' });
|
||||
}).finally(() => {
|
||||
this.createLoading = false;
|
||||
uni.hideLoading();
|
||||
});
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.form = {};
|
||||
this.initData();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -1,108 +0,0 @@
|
|||
<template>
|
||||
<view>
|
||||
<view style="padding: 0 24rpx;">
|
||||
<u--form labelWidth="75">
|
||||
<u-form-item label="产品编号:" borderBottom>
|
||||
<u--input border="none" :focus="inputFocus1" @focus="inputFocus1 = true" @blur="inputFocus1 = false"
|
||||
suffixIcon="scan" selectionStart="0" selectionEnd="999" placeholder="请扫描产品条形码" @confirm="validateGoods">
|
||||
</u--input>
|
||||
</u-form-item>
|
||||
<u-form-item label="产品名称:" borderBottom>
|
||||
<u--input :value="stockInGoodsItem.goods_name" border="none" :disabled="true"></u--input>
|
||||
</u-form-item>
|
||||
<u-form-item v-if="stockInGoodsItem.enable_batch_control" label="批次编号:" borderBottom
|
||||
@click="showPicker = true;">
|
||||
<u--input :value="stockInGoodsItem.batch_number" border="none" :disabled="true"></u--input>
|
||||
</u-form-item>
|
||||
<u-form-item label="剩余数量:" borderBottom>
|
||||
<u--input :value="stockInGoodsItem.remain_quantity" border="none" :disabled="true"></u--input>
|
||||
</u-form-item>
|
||||
<u-form-item label="出库数量:" borderBottom>
|
||||
<u--input v-model="stockInGoodsItem.stockInQuantity" border="none"></u--input>
|
||||
</u-form-item>
|
||||
<u-form-item v-if="stockInGoodsItem.enable_batch_control" label="批次编号:" borderBottom>
|
||||
<u--input :value="stockInGoodsItem.batch_number" border="none"></u--input>
|
||||
</u-form-item>
|
||||
<u-form-item v-if="stockInGoodsItem.enable_batch_control" label="生产日期:" borderBottom
|
||||
@click="showDatePicker = true">
|
||||
<u--input :value="stockInGoodsItem.production_date" border="none" :disabled="true"></u--input>
|
||||
</u-form-item>
|
||||
</u--form>
|
||||
</view>
|
||||
|
||||
<view style="position: absolute; bottom: 0; width: 100%;">
|
||||
<u-button type="primary" size="large" text="确定" @click="confirm"></u-button>
|
||||
</view>
|
||||
|
||||
<u-datetime-picker :show="showDatePicker" mode="date" :closeOnClickOverlay="true" @close="showDatePicker = false"
|
||||
@cancel="showDatePicker = false" @confirm="selectDatePicker">
|
||||
</u-datetime-picker>
|
||||
|
||||
<u-toast ref="uToast"></u-toast>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { batchOption } from '@/api/option.js';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
eventChannel: null,
|
||||
stockInGoodsItem: {},
|
||||
showDatePicker: false,
|
||||
showPicker: false,
|
||||
inputFocus1: true,
|
||||
batchItems: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
validateGoods(value) {
|
||||
if (this.stockInGoodsItem.goods_number == value) {
|
||||
this.stockInGoodsItem.stockInQuantity += 1;
|
||||
this.$refs.uToast.show({ message: '扫码成功' });
|
||||
} else {
|
||||
this.$refs.uToast.show({ message: '产品错误' });
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
this.inputFocus1 = true;
|
||||
}, 500);
|
||||
},
|
||||
confirm() {
|
||||
if (this.stockInGoodsItem.enable_batch_control && !this.stockInGoodsItem.batch_number &&
|
||||
this.stockInGoodsItem.batch_number.length == 0) {
|
||||
this.$refs.uToast.show({ message: '请输入批次' });
|
||||
return;
|
||||
}
|
||||
|
||||
this.stockInGoodsItem.isConfirmed = true;
|
||||
this.eventChannel.emit('confirm', this.stockInGoodsItem);
|
||||
uni.navigateBack();
|
||||
},
|
||||
selectDatePicker(item) {
|
||||
this.stockInGoodsItem.production_date = this.formatDate(item.value);
|
||||
this.showDatePicker = false;
|
||||
},
|
||||
formatDate(datetime) {
|
||||
let date = new Date(datetime);
|
||||
let year = date.getFullYear(),
|
||||
month = ("0" + (date.getMonth() + 1)).slice(-2),
|
||||
sdate = ("0" + date.getDate()).slice(-2);
|
||||
return year + "-" + month + "-" + sdate;
|
||||
},
|
||||
},
|
||||
onLoad(option) {
|
||||
this.eventChannel = this.getOpenerEventChannel();
|
||||
this.stockInGoodsItem = JSON.parse(decodeURIComponent(option.item));
|
||||
if (this.stockInGoodsItem.enable_batch_control) {
|
||||
this.getBatch();
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -1,241 +0,0 @@
|
|||
<template>
|
||||
<view>
|
||||
<view style="padding: 0 24rpx;">
|
||||
<u--form labelWidth="110">
|
||||
<u-form-item label="入库单号:" borderBottom>
|
||||
<u--input border="none" :focus="inputFocus1" @focus="inputFocus1 = true" @blur="inputFocus1 = false"
|
||||
suffixIcon="scan" selectionStart="0" selectionEnd="999" placeholder="请扫描入库单条形码" @confirm="getStockInOrder">
|
||||
</u--input>
|
||||
</u-form-item>
|
||||
<u-form-item label="应入数量:" borderBottom>
|
||||
<u--input :value="totalQuantity" border="none" :disabled="true"></u--input>
|
||||
</u-form-item>
|
||||
<u-form-item label="已入数量:" borderBottom>
|
||||
<u--input :value="inBoundQuantity" border="none" :disabled="true"></u--input>
|
||||
</u-form-item>
|
||||
|
||||
<u-form-item label="产品编号:" borderBottom>
|
||||
<u--input border="none" :focus="inputFocus2" @focus="inputFocus2 = true" @blur="inputFocus2 = false"
|
||||
suffixIcon="scan" selectionStart="0" selectionEnd="999" placeholder="请扫描产品条形码" @confirm="scanGoods">
|
||||
</u--input>
|
||||
</u-form-item>
|
||||
</u--form>
|
||||
|
||||
<u-cell v-for="(item, index) in stockInGoodsItems" isLink @click="goGoodsForm(item)">
|
||||
<view class="cell-title" slot="title">{{ item.goods_number }}</view>
|
||||
<view class="cell-label" slot="label">
|
||||
<u-row class="cell-label-row">
|
||||
<u-col span="6">产品名称: {{ item.goods_name }}</u-col>
|
||||
<u-col span="6">产品条码: {{ item.goods_barcode }}</u-col>
|
||||
</u-row>
|
||||
<u-row class="cell-label-row">
|
||||
<u-col span="6">应收数量: {{ item.remain_quantity }}</u-col>
|
||||
<u-col span="6">已收数量: {{ item.stockInQuantity }}</u-col>
|
||||
</u-row>
|
||||
</view>
|
||||
</u-cell>
|
||||
</view>
|
||||
|
||||
<view style="position: absolute; bottom: 0; width: 100%;">
|
||||
<u-button type="primary" size="large" text="确认入库" :disabled="createLoading" :loading="createLoading"
|
||||
@click="create">
|
||||
</u-button>
|
||||
</view>
|
||||
|
||||
<u-toast ref="uToast"></u-toast>
|
||||
<u-picker :show="showPicker" :columns="[batchItems]" keyName="batch_number" :closeOnClickOverlay="true"
|
||||
@close="showPicker = false" @cancel="showPicker = false" @confirm="selectBatch"></u-picker>
|
||||
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { stockInTaskList, stockInRecordCreate } from '@/api/stockIn.js';
|
||||
import { mapState, mapMutations } from 'vuex';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
stockInOrderItem: {},
|
||||
stockInGoodsItems: [],
|
||||
createLoading: false,
|
||||
batchItems: [],
|
||||
showPicker: false,
|
||||
createLoading: false,
|
||||
inputFocus1: true,
|
||||
inputFocus2: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
userItem: (state) => state.system.userItem,
|
||||
warehouseItems: (state) => state.system.warehouseItems,
|
||||
currentWarehouse: (state) => state.system.currentWarehouse,
|
||||
}),
|
||||
totalQuantity() {
|
||||
let totalQuantity = 0;
|
||||
for (let item of this.stockInGoodsItems) {
|
||||
totalQuantity += Number(item.remain_quantity);
|
||||
}
|
||||
return totalQuantity
|
||||
},
|
||||
inBoundQuantity() {
|
||||
let inBoundQuantity = 0;
|
||||
for (let item of this.stockInGoodsItems) {
|
||||
inBoundQuantity += Number(item.stockInQuantity);
|
||||
}
|
||||
return inBoundQuantity
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getStockInOrder(value) {
|
||||
let searchForm = {
|
||||
search: value,
|
||||
page: 1,
|
||||
page_size: 6,
|
||||
is_completed: false,
|
||||
is_void: false
|
||||
};
|
||||
|
||||
uni.showLoading({ title: '查询中' });
|
||||
stockInTaskList(searchForm).then((data) => {
|
||||
if (data.count == 1) {
|
||||
this.stockInOrderItem = data.results[0];
|
||||
let stockInGoodsItems = [];
|
||||
for (let item of this.stockInOrderItem.stock_in_goods_items) {
|
||||
item.stockInQuantity = 0;
|
||||
item.isConfirmed = false;
|
||||
item.warehouse = this.stockInOrderItem.warehouse;
|
||||
stockInGoodsItems.push(item);
|
||||
}
|
||||
this.stockInGoodsItems = stockInGoodsItems;
|
||||
this.inputFocus2 = true;
|
||||
} else {
|
||||
this.$refs.uToast.show({ message: `入库通知单号[${value}]不存在` });
|
||||
setTimeout(() => {
|
||||
this.inputFocus1 = true;
|
||||
}, 500);
|
||||
}
|
||||
}).finally(() => {
|
||||
uni.hideLoading();
|
||||
});
|
||||
},
|
||||
goGoodsForm(stockInGoodsItem) {
|
||||
const updateReceivedGoods = (item) => {
|
||||
let index = this.stockInGoodsItems.findIndex(_item => _item.id == item.id);
|
||||
if (index == -1) {
|
||||
this.stockInGoodsItems.splice(0, 0, item);
|
||||
} else {
|
||||
this.stockInGoodsItems.splice(index, 1, item);
|
||||
}
|
||||
}
|
||||
|
||||
uni.navigateTo({
|
||||
url: '../stockInForm/index?item=' + encodeURIComponent(JSON.stringify(stockInGoodsItem)),
|
||||
events: {
|
||||
confirm(item) {
|
||||
updateReceivedGoods(item);
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
scanGoods(value) {
|
||||
let batchItems = [];
|
||||
for (let item of this.stockInGoodsItems) {
|
||||
if (item.goods_number == value) {
|
||||
batchItems.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
if (batchItems.length == 0) {
|
||||
this.$refs.uToast.show({ message: `物料[${value}]不存在` });
|
||||
setTimeout(() => {
|
||||
this.inputFocus2 = true;
|
||||
}, 500);
|
||||
|
||||
} else if (batchItems.length == 1) {
|
||||
let batchItem = batchItems[0];
|
||||
this.goGoodsForm(batchItem);
|
||||
} else {
|
||||
this.batchItems = batchItems;
|
||||
this.showPicker = true;
|
||||
}
|
||||
},
|
||||
selectBatch(item) {
|
||||
this.showPicker = false;
|
||||
let batchItem = this.batchItems[item.indexs[0]];
|
||||
this.goGoodsForm(batchItem);
|
||||
},
|
||||
getTime() {
|
||||
let date = new Date(),
|
||||
year = date.getFullYear(),
|
||||
month = date.getMonth() + 1,
|
||||
day = date.getDate(),
|
||||
hour = date.getHours() < 10 ? "0" + date.getHours() : date.getHours(),
|
||||
minute = date.getMinutes() < 10 ? "0" + date.getMinutes() : date.getMinutes(),
|
||||
second = date.getSeconds() < 10 ? "0" + date.getSeconds() : date.getSeconds();
|
||||
month >= 1 && month <= 9 ? (month = "0" + month) : "";
|
||||
day >= 0 && day <= 9 ? (day = "0" + day) : "";
|
||||
let timer = year + '-' + month + '-' + day;
|
||||
return timer;
|
||||
},
|
||||
create() {
|
||||
let createForm = {
|
||||
stock_in_order: this.stockInOrderItem.id,
|
||||
handler: this.userItem.id,
|
||||
handle_time: this.getTime(),
|
||||
stock_in_record_goods_items: [],
|
||||
};
|
||||
|
||||
for (let item of this.stockInGoodsItems) {
|
||||
if (item.isConfirmed && item.stockInQuantity > 0) {
|
||||
createForm.stock_in_record_goods_items.push({
|
||||
stock_in_goods: item.id,
|
||||
batch_number: item.batch_number,
|
||||
stock_in_quantity: item.stockInQuantity,
|
||||
production_date: item.production_date,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (createForm.stock_in_record_goods_items.length == 0) {
|
||||
this.$refs.uToast.show({ message: '请添加入库产品' });
|
||||
return
|
||||
}
|
||||
|
||||
uni.showLoading({ title: '创建中' });
|
||||
this.createLoading = true;
|
||||
stockInRecordCreate(createForm).then(() => {
|
||||
uni.redirectTo({ url: '../stockInTask/index' });
|
||||
}).finally(() => {
|
||||
this.createLoading = false;
|
||||
uni.hideLoading();
|
||||
});
|
||||
},
|
||||
},
|
||||
onShow() {
|
||||
if (this.stockInGoodsItems.length > 0) {
|
||||
setTimeout(() => {
|
||||
this.inputFocus2 = true;
|
||||
}, 500);
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.cell-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.cell-label {
|
||||
font-size: 24rpx;
|
||||
color: #0005;
|
||||
}
|
||||
|
||||
.cell-label-row {
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
</style>
|
|
@ -1,109 +0,0 @@
|
|||
<template>
|
||||
<view>
|
||||
<view style="padding: 0 24rpx;">
|
||||
<u--form labelWidth="75">
|
||||
<u-form-item label="产品编号:" borderBottom>
|
||||
<u--input border="none" :focus="inputFocus1" @focus="inputFocus1 = true" @blur="inputFocus1 = false"
|
||||
suffixIcon="scan" selectionStart="0" selectionEnd="999" placeholder="请扫描产品条形码" @confirm="validateGoods">
|
||||
</u--input>
|
||||
</u-form-item>
|
||||
<u-form-item label="产品名称:" borderBottom>
|
||||
<u--input :value="stockOutGoodsItem.goods_name" border="none" :disabled="true"></u--input>
|
||||
</u-form-item>
|
||||
<u-form-item v-if="stockOutGoodsItem.enable_batch_control" label="批次编号:" borderBottom
|
||||
@click="showPicker = true;">
|
||||
<u--input :value="stockOutGoodsItem.batch_number" border="none" :disabled="true"></u--input>
|
||||
</u-form-item>
|
||||
<u-form-item label="剩余数量:" borderBottom>
|
||||
<u--input :value="stockOutGoodsItem.remain_quantity" border="none" :disabled="true"></u--input>
|
||||
</u-form-item>
|
||||
<u-form-item label="出库数量:" borderBottom>
|
||||
<u--input v-model="stockOutGoodsItem.stockOutQuantity" border="none"></u--input>
|
||||
</u-form-item>
|
||||
</u--form>
|
||||
</view>
|
||||
|
||||
<view style="position: absolute; bottom: 0; width: 100%;">
|
||||
<u-button type="primary" size="large" text="确定" @click="confirm"></u-button>
|
||||
</view>
|
||||
|
||||
<u-picker :show="showPicker" :columns="[batchItems]" keyName="number" :closeOnClickOverlay="true"
|
||||
@close="showPicker = false" @cancel="showPicker = false" @confirm="selectPicker"></u-picker>
|
||||
|
||||
<u-toast ref="uToast"></u-toast>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { batchOption } from '@/api/option.js';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
eventChannel: null,
|
||||
stockOutGoodsItem: {},
|
||||
showDatePicker: false,
|
||||
showPicker: false,
|
||||
inputFocus1: true,
|
||||
batchItems: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
validateGoods(value) {
|
||||
if (this.stockOutGoodsItem.goods_number == value) {
|
||||
this.stockOutGoodsItem.stockOutQuantity += 1;
|
||||
this.$refs.uToast.show({ message: '扫码成功' });
|
||||
} else {
|
||||
this.$refs.uToast.show({ message: '产品错误' });
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
this.inputFocus1 = true;
|
||||
}, 500);
|
||||
},
|
||||
confirm() {
|
||||
if (this.stockOutGoodsItem.enable_batch_control && !this.stockOutGoodsItem.batch) {
|
||||
this.$refs.uToast.show({ message: '请选择批次' });
|
||||
return;
|
||||
}
|
||||
|
||||
this.stockOutGoodsItem.isConfirmed = true;
|
||||
this.eventChannel.emit('confirm', this.stockOutGoodsItem);
|
||||
uni.navigateBack();
|
||||
},
|
||||
selectPicker(item) {
|
||||
let index = item.indexs[0];
|
||||
let batchItem = this.batchItems[index];
|
||||
this.stockOutGoodsItem.batch = batchItem.id;
|
||||
this.stockOutGoodsItem.batch_number = `${batchItem.number} | 库存: ${batchItem.remain_quantity}`;
|
||||
this.showPicker = false;
|
||||
},
|
||||
getBatch() {
|
||||
let searchForm = {
|
||||
page: 1,
|
||||
page_size: 999999,
|
||||
has_stock: true,
|
||||
warehouse: this.stockOutGoodsItem.warehouse,
|
||||
goods: this.stockOutGoodsItem.goods,
|
||||
};
|
||||
|
||||
batchOption(searchForm).then((data) => {
|
||||
this.batchItems = data.results;
|
||||
console.log('---------', this.batchItems)
|
||||
});
|
||||
},
|
||||
},
|
||||
onLoad(option) {
|
||||
this.eventChannel = this.getOpenerEventChannel();
|
||||
this.stockOutGoodsItem = JSON.parse(decodeURIComponent(option.item));
|
||||
if (this.stockOutGoodsItem.enable_batch_control) {
|
||||
this.getBatch();
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -1,240 +0,0 @@
|
|||
<template>
|
||||
<view>
|
||||
<view style="padding: 0 24rpx;">
|
||||
<u--form labelWidth="110">
|
||||
<u-form-item label="出库单号:" borderBottom>
|
||||
<u--input border="none" :focus="inputFocus1" @focus="inputFocus1 = true" @blur="inputFocus1 = false"
|
||||
suffixIcon="scan" selectionStart="0" selectionEnd="999" placeholder="请扫描出库单条形码" @confirm="getStockOutOrder">
|
||||
</u--input>
|
||||
</u-form-item>
|
||||
<u-form-item label="应出数量:" borderBottom>
|
||||
<u--input :value="totalQuantity" border="none" :disabled="true"></u--input>
|
||||
</u-form-item>
|
||||
<u-form-item label="已出数量:" borderBottom>
|
||||
<u--input :value="outBoundQuantity" border="none" :disabled="true"></u--input>
|
||||
</u-form-item>
|
||||
|
||||
<u-form-item label="产品编号:" borderBottom>
|
||||
<u--input border="none" :focus="inputFocus2" @focus="inputFocus2 = true" @blur="inputFocus2 = false"
|
||||
suffixIcon="scan" selectionStart="0" selectionEnd="999" placeholder="请扫描产品条形码" @confirm="scanGoods">
|
||||
</u--input>
|
||||
</u-form-item>
|
||||
</u--form>
|
||||
|
||||
<u-cell v-for="(item, index) in stockOutGoodsItems" isLink @click="goGoodsForm(item)">
|
||||
<view class="cell-title" slot="title">{{ item.goods_number }}</view>
|
||||
<view class="cell-label" slot="label">
|
||||
<u-row class="cell-label-row">
|
||||
<u-col span="6">产品名称: {{ item.goods_name }}</u-col>
|
||||
<u-col span="6">产品条码: {{ item.goods_barcode }}</u-col>
|
||||
</u-row>
|
||||
<u-row class="cell-label-row">
|
||||
<u-col span="6">应收数量: {{ item.remain_quantity }}</u-col>
|
||||
<u-col span="6">已收数量: {{ item.stockOutQuantity }}</u-col>
|
||||
</u-row>
|
||||
</view>
|
||||
</u-cell>
|
||||
</view>
|
||||
|
||||
<view style="position: absolute; bottom: 0; width: 100%;">
|
||||
<u-button type="primary" size="large" text="确认出库" :disabled="createLoading" :loading="createLoading"
|
||||
@click="create">
|
||||
</u-button>
|
||||
</view>
|
||||
|
||||
<u-toast ref="uToast"></u-toast>
|
||||
<u-picker :show="showPicker" :columns="[batchItems]" keyName="batch_number" :closeOnClickOverlay="true"
|
||||
@close="showPicker = false" @cancel="showPicker = false" @confirm="selectBatch"></u-picker>
|
||||
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { stockOutTaskList, stockOutRecordCreate } from '@/api/stockOut.js';
|
||||
import { mapState, mapMutations } from 'vuex';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
stockOutOrderItem: {},
|
||||
stockOutGoodsItems: [],
|
||||
createLoading: false,
|
||||
batchItems: [],
|
||||
showPicker: false,
|
||||
createLoading: false,
|
||||
inputFocus1: true,
|
||||
inputFocus2: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
userItem: (state) => state.system.userItem,
|
||||
warehouseItems: (state) => state.system.warehouseItems,
|
||||
currentWarehouse: (state) => state.system.currentWarehouse,
|
||||
}),
|
||||
totalQuantity() {
|
||||
let totalQuantity = 0;
|
||||
for (let item of this.stockOutGoodsItems) {
|
||||
totalQuantity += Number(item.remain_quantity);
|
||||
}
|
||||
return totalQuantity
|
||||
},
|
||||
outBoundQuantity() {
|
||||
let outBoundQuantity = 0;
|
||||
for (let item of this.stockOutGoodsItems) {
|
||||
outBoundQuantity += Number(item.stockOutQuantity);
|
||||
}
|
||||
return outBoundQuantity
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getStockOutOrder(value) {
|
||||
let searchForm = {
|
||||
search: value,
|
||||
page: 1,
|
||||
page_size: 6,
|
||||
is_completed: false,
|
||||
is_void: false
|
||||
};
|
||||
|
||||
uni.showLoading({ title: '查询中' });
|
||||
stockOutTaskList(searchForm).then((data) => {
|
||||
if (data.count == 1) {
|
||||
this.stockOutOrderItem = data.results[0];
|
||||
let stockOutGoodsItems = [];
|
||||
for (let item of this.stockOutOrderItem.stock_out_goods_items) {
|
||||
item.stockOutQuantity = 0;
|
||||
item.isConfirmed = false;
|
||||
item.warehouse = this.stockOutOrderItem.warehouse;
|
||||
stockOutGoodsItems.push(item);
|
||||
}
|
||||
this.stockOutGoodsItems = stockOutGoodsItems;
|
||||
this.inputFocus2 = true;
|
||||
} else {
|
||||
this.$refs.uToast.show({ message: `出库通知单号[${value}]不存在` });
|
||||
setTimeout(() => {
|
||||
this.inputFocus1 = true;
|
||||
}, 500);
|
||||
}
|
||||
}).finally(() => {
|
||||
uni.hideLoading();
|
||||
});
|
||||
},
|
||||
goGoodsForm(stockOutGoodsItem) {
|
||||
const updateReceivedGoods = (item) => {
|
||||
let index = this.stockOutGoodsItems.findIndex(_item => _item.id == item.id);
|
||||
if (index == -1) {
|
||||
this.stockOutGoodsItems.splice(0, 0, item);
|
||||
} else {
|
||||
this.stockOutGoodsItems.splice(index, 1, item);
|
||||
}
|
||||
}
|
||||
|
||||
uni.navigateTo({
|
||||
url: '../stockOutForm/index?item=' + encodeURIComponent(JSON.stringify(stockOutGoodsItem)),
|
||||
events: {
|
||||
confirm(item) {
|
||||
updateReceivedGoods(item);
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
scanGoods(value) {
|
||||
let batchItems = [];
|
||||
for (let item of this.stockOutGoodsItems) {
|
||||
if (item.goods_number == value) {
|
||||
batchItems.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
if (batchItems.length == 0) {
|
||||
this.$refs.uToast.show({ message: `物料[${value}]不存在` });
|
||||
setTimeout(() => {
|
||||
this.inputFocus2 = true;
|
||||
}, 500);
|
||||
|
||||
} else if (batchItems.length == 1) {
|
||||
let batchItem = batchItems[0];
|
||||
this.goGoodsForm(batchItem);
|
||||
} else {
|
||||
this.batchItems = batchItems;
|
||||
this.showPicker = true;
|
||||
}
|
||||
},
|
||||
selectBatch(item) {
|
||||
this.showPicker = false;
|
||||
let batchItem = this.batchItems[item.indexs[0]];
|
||||
this.goGoodsForm(batchItem);
|
||||
},
|
||||
getTime() {
|
||||
let date = new Date(),
|
||||
year = date.getFullYear(),
|
||||
month = date.getMonth() + 1,
|
||||
day = date.getDate(),
|
||||
hour = date.getHours() < 10 ? "0" + date.getHours() : date.getHours(),
|
||||
minute = date.getMinutes() < 10 ? "0" + date.getMinutes() : date.getMinutes(),
|
||||
second = date.getSeconds() < 10 ? "0" + date.getSeconds() : date.getSeconds();
|
||||
month >= 1 && month <= 9 ? (month = "0" + month) : "";
|
||||
day >= 0 && day <= 9 ? (day = "0" + day) : "";
|
||||
let timer = year + '-' + month + '-' + day;
|
||||
return timer;
|
||||
},
|
||||
create() {
|
||||
let createForm = {
|
||||
stock_out_order: this.stockOutOrderItem.id,
|
||||
handler: this.userItem.id,
|
||||
handle_time: this.getTime(),
|
||||
stock_out_record_goods_items: [],
|
||||
};
|
||||
|
||||
for (let item of this.stockOutGoodsItems) {
|
||||
if (item.isConfirmed && item.stockOutQuantity > 0) {
|
||||
createForm.stock_out_record_goods_items.push({
|
||||
stock_out_goods: item.id,
|
||||
batch: item.batch,
|
||||
stock_out_quantity: item.stockOutQuantity,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (createForm.stock_out_record_goods_items.length == 0) {
|
||||
this.$refs.uToast.show({ message: '请添加收货物料' });
|
||||
return
|
||||
}
|
||||
|
||||
uni.showLoading({ title: '创建中' });
|
||||
this.createLoading = true;
|
||||
stockOutRecordCreate(createForm).then(() => {
|
||||
uni.redirectTo({ url: '../stockOutTask/index' });
|
||||
}).finally(() => {
|
||||
this.createLoading = false;
|
||||
uni.hideLoading();
|
||||
});
|
||||
},
|
||||
},
|
||||
onShow() {
|
||||
if (this.stockOutGoodsItems.length > 0) {
|
||||
setTimeout(() => {
|
||||
this.inputFocus2 = true;
|
||||
}, 500);
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.cell-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.cell-label {
|
||||
font-size: 24rpx;
|
||||
color: #0005;
|
||||
}
|
||||
|
||||
.cell-label-row {
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
</style>
|
Before Width: | Height: | Size: 5.7 KiB |
Before Width: | Height: | Size: 5.7 KiB |
Before Width: | Height: | Size: 5.7 KiB |
Before Width: | Height: | Size: 4.9 KiB |
Before Width: | Height: | Size: 209 KiB |
Before Width: | Height: | Size: 81 KiB |
Before Width: | Height: | Size: 4.5 KiB |
|
@ -1,11 +0,0 @@
|
|||
import system from './modules/system';
|
||||
import Vuex from 'vuex';
|
||||
import Vue from 'vue';
|
||||
|
||||
Vue.use(Vuex);
|
||||
|
||||
export default new Vuex.Store({
|
||||
modules: {
|
||||
system,
|
||||
},
|
||||
})
|
|
@ -1,18 +0,0 @@
|
|||
export default {
|
||||
state: () => ({
|
||||
userItem: {},
|
||||
warehouseItems: [],
|
||||
currentWarehouse: undefined,
|
||||
}),
|
||||
mutations: {
|
||||
setUserItem(state, data) {
|
||||
state.userItem = data;
|
||||
},
|
||||
setWarehouseItems(state, data) {
|
||||
state.warehouseItems = data;
|
||||
},
|
||||
setCurrentWarehouse(state, data) {
|
||||
state.currentWarehouse = data;
|
||||
},
|
||||
}
|
||||
}
|
|
@ -1,77 +0,0 @@
|
|||
/**
|
||||
* 这里是uni-app内置的常用样式变量
|
||||
*
|
||||
* uni-app 官方扩展插件及插件市场(https://ext.dcloud.net.cn)上很多三方插件均使用了这些样式变量
|
||||
* 如果你是插件开发者,建议你使用scss预处理,并在插件代码中直接使用这些变量(无需 import 这个文件),方便用户通过搭积木的方式开发整体风格一致的App
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* 如果你是App开发者(插件使用者),你可以通过修改这些变量来定制自己的插件主题,实现自定义主题功能
|
||||
*
|
||||
* 如果你的项目同样使用了scss预处理,你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件
|
||||
*/
|
||||
|
||||
/* 颜色变量 */
|
||||
@import '@/uni_modules/uview-ui/theme.scss';
|
||||
|
||||
/* 行为相关颜色 */
|
||||
$uni-color-primary: #007aff;
|
||||
$uni-color-success: #4cd964;
|
||||
$uni-color-warning: #f0ad4e;
|
||||
$uni-color-error: #dd524d;
|
||||
|
||||
/* 文字基本颜色 */
|
||||
$uni-text-color:#333;//基本色
|
||||
$uni-text-color-inverse:#fff;//反色
|
||||
$uni-text-color-grey:#999;//辅助灰色,如加载更多的提示信息
|
||||
$uni-text-color-placeholder: #808080;
|
||||
$uni-text-color-disable:#c0c0c0;
|
||||
|
||||
/* 背景颜色 */
|
||||
$uni-bg-color:#ffffff;
|
||||
$uni-bg-color-grey:#f8f8f8;
|
||||
$uni-bg-color-hover:#f1f1f1;//点击状态颜色
|
||||
$uni-bg-color-mask:rgba(0, 0, 0, 0.4);//遮罩颜色
|
||||
|
||||
/* 边框颜色 */
|
||||
$uni-border-color:#c8c7cc;
|
||||
|
||||
/* 尺寸变量 */
|
||||
|
||||
/* 文字尺寸 */
|
||||
$uni-font-size-sm:12px;
|
||||
$uni-font-size-base:14px;
|
||||
$uni-font-size-lg:16;
|
||||
|
||||
/* 图片尺寸 */
|
||||
$uni-img-size-sm:20px;
|
||||
$uni-img-size-base:26px;
|
||||
$uni-img-size-lg:40px;
|
||||
|
||||
/* Border Radius */
|
||||
$uni-border-radius-sm: 2px;
|
||||
$uni-border-radius-base: 3px;
|
||||
$uni-border-radius-lg: 6px;
|
||||
$uni-border-radius-circle: 50%;
|
||||
|
||||
/* 水平间距 */
|
||||
$uni-spacing-row-sm: 5px;
|
||||
$uni-spacing-row-base: 10px;
|
||||
$uni-spacing-row-lg: 15px;
|
||||
|
||||
/* 垂直间距 */
|
||||
$uni-spacing-col-sm: 4px;
|
||||
$uni-spacing-col-base: 8px;
|
||||
$uni-spacing-col-lg: 12px;
|
||||
|
||||
/* 透明度 */
|
||||
$uni-opacity-disabled: 0.3; // 组件禁用态的透明度
|
||||
|
||||
/* 文章场景相关 */
|
||||
$uni-color-title: #2C405A; // 文章标题颜色
|
||||
$uni-font-size-title:20px;
|
||||
$uni-color-subtitle: #555555; // 二级标题颜色
|
||||
$uni-font-size-subtitle:26px;
|
||||
$uni-color-paragraph: #3F536E; // 文章段落颜色
|
||||
$uni-font-size-paragraph:15px;
|
|
@ -1,21 +0,0 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2020 www.uviewui.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -1,104 +0,0 @@
|
|||
<p align="center">
|
||||
<img alt="logo" src="https://uviewui.com/common/logo.png" width="120" height="120" style="margin-bottom: 10px;">
|
||||
</p>
|
||||
<h3 align="center" style="margin: 30px 0 30px;font-weight: bold;font-size:40px;">uView</h3>
|
||||
<h3 align="center">多平台快速开发的UI框架</h3>
|
||||
|
||||
## 说明
|
||||
|
||||
uView UI,是[uni-app](https://uniapp.dcloud.io/)生态优秀的UI框架,全面的组件和便捷的工具会让您信手拈来,如鱼得水
|
||||
|
||||
## 特性
|
||||
|
||||
- 兼容安卓,iOS,微信小程序,H5,QQ小程序,百度小程序,支付宝小程序,头条小程序
|
||||
- 60+精选组件,功能丰富,多端兼容,让您快速集成,开箱即用
|
||||
- 众多贴心的JS利器,让您飞镖在手,召之即来,百步穿杨
|
||||
- 众多的常用页面和布局,让您专注逻辑,事半功倍
|
||||
- 详尽的文档支持,现代化的演示效果
|
||||
- 按需引入,精简打包体积
|
||||
|
||||
|
||||
## 安装
|
||||
|
||||
```bash
|
||||
# npm方式安装,插件市场导入无需执行此命令
|
||||
npm i uview-ui
|
||||
```
|
||||
|
||||
## 快速上手
|
||||
|
||||
1. `main.js`引入uView库
|
||||
```js
|
||||
// main.js
|
||||
import uView from 'uview-ui';
|
||||
Vue.use(uView);
|
||||
```
|
||||
|
||||
2. `App.vue`引入基础样式(注意style标签需声明scss属性支持)
|
||||
```css
|
||||
/* App.vue */
|
||||
<style lang="scss">
|
||||
@import "uview-ui/index.scss";
|
||||
</style>
|
||||
```
|
||||
|
||||
3. `uni.scss`引入全局scss变量文件
|
||||
```css
|
||||
/* uni.scss */
|
||||
@import "uview-ui/theme.scss";
|
||||
```
|
||||
|
||||
4. `pages.json`配置easycom规则(按需引入)
|
||||
|
||||
```js
|
||||
// pages.json
|
||||
{
|
||||
"easycom": {
|
||||
// npm安装的方式不需要前面的"@/",下载安装的方式需要"@/"
|
||||
// npm安装方式
|
||||
"^u-(.*)": "uview-ui/components/u-$1/u-$1.vue"
|
||||
// 下载安装方式
|
||||
// "^u-(.*)": "@/uview-ui/components/u-$1/u-$1.vue"
|
||||
},
|
||||
// 此为本身已有的内容
|
||||
"pages": [
|
||||
// ......
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
请通过[快速上手](https://www.uviewui.com/components/quickstart.html)了解更详细的内容
|
||||
|
||||
## 使用方法
|
||||
配置easycom规则后,自动按需引入,无需`import`组件,直接引用即可。
|
||||
|
||||
```html
|
||||
<template>
|
||||
<u-button text="按钮"></u-button>
|
||||
</template>
|
||||
```
|
||||
|
||||
请通过[快速上手](https://www.uviewui.com/components/quickstart.html)了解更详细的内容
|
||||
|
||||
## 链接
|
||||
|
||||
- [官方文档](https://www.uviewui.com/)
|
||||
- [更新日志](https://www.www.uviewui.com/components/changelog.html)
|
||||
- [升级指南](https://www.uviewui.com/components/changelog.html)
|
||||
- [关于我们](https://www.uviewui.com/cooperation/about.html)
|
||||
|
||||
## 预览
|
||||
|
||||
您可以通过**微信**扫码,查看最佳的演示效果。
|
||||
<br>
|
||||
<br>
|
||||
<img src="https://uviewui.com/common/weixin_mini_qrcode.png" width="220" height="220" >
|
||||
|
||||
## 捐赠uView的研发
|
||||
|
||||
uView文档和源码全部开源免费,如果您认为uView帮到了您的开发工作,您可以捐赠uView的研发工作,捐赠无门槛,哪怕是一杯可乐也好(相信这比打赏主播更有意义)。
|
||||
|
||||
<img src="https://uviewui.com/common/alipay.png" width="220" ><img style="margin-left: 100px;" src="https://uviewui.com/common/wechat.png" width="220" >
|
||||
|
||||
## 版权信息
|
||||
uView遵循[MIT](https://en.wikipedia.org/wiki/MIT_License)开源协议,意味着您无需支付任何费用,也无需授权,即可将uView应用到您的产品中。
|
|
@ -1,293 +0,0 @@
|
|||
## 2.0.29(2022-03-13)
|
||||
# uView2.0重磅发布,利剑出鞘,一统江湖
|
||||
|
||||
1. 修复`u--text`组件设置`decoration`属性未生效的问题
|
||||
2. 修复`u-datetime-picker`使用`formatter`后返回值不正确
|
||||
3. 修复`u-datetime-picker` `intercept` 可能为undefined
|
||||
4. 修复已设置单位 uni..config.unit = 'rpx'时,线型指示器 `transform` 的位置翻倍,导致指示器超出宽度
|
||||
5. 修复mixin中bem方法生成的类名在支付宝和字节小程序中失效
|
||||
6. 修复默认值传值为空的时候,打开`u-datetime-picker`报错,不能选中第一列时间的bug
|
||||
7. 修复`u-datetime-picker`使用`formatter`后返回值不正确
|
||||
8. 修复`u-image`组件`loading`无效果的问题
|
||||
9. 修复`config.unit`属性设为`rpx`时,导航栏占用高度不足导致塌陷的问题
|
||||
10. 修复`u-datetime-picker`组件`itemHeight`无效问题
|
||||
11. 其他修复
|
||||
## 2.0.28(2022-02-22)
|
||||
# uView2.0重磅发布,利剑出鞘,一统江湖
|
||||
|
||||
1. search组件新增searchIconSize属性
|
||||
2. 兼容Safari/Webkit中传入时间格式如2022-02-17 12:00:56
|
||||
3. 修复text value.js 判断日期出format错误问题
|
||||
4. priceFormat格式化金额出现精度错误
|
||||
5. priceFormat在部分情况下出现精度损失问题
|
||||
6. 优化表单rules提示
|
||||
7. 修复avatar组件src为空时,展示状态不对
|
||||
8. 其他修复
|
||||
## 2.0.27(2022-01-28)
|
||||
# uView2.0重磅发布,利剑出鞘,一统江湖
|
||||
|
||||
1.样式修复
|
||||
## 2.0.26(2022-01-28)
|
||||
# uView2.0重磅发布,利剑出鞘,一统江湖
|
||||
|
||||
1.样式修复
|
||||
## 2.0.25(2022-01-27)
|
||||
# uView2.0重磅发布,利剑出鞘,一统江湖
|
||||
|
||||
1. 修复text组件mode=price时,可能会导致精度错误的问题
|
||||
2. 添加$u.setConfig()方法,可设置uView内置的config, props, zIndex, color属性,详见:[修改uView内置配置方案](https://uviewui.com/components/setting.html#%E9%BB%98%E8%AE%A4%E5%8D%95%E4%BD%8D%E9%85%8D%E7%BD%AE)
|
||||
3. 优化form组件在errorType=toast时,如果输入错误页面会有抖动的问题
|
||||
4. 修复$u.addUnit()对配置默认单位可能无效的问题
|
||||
## 2.0.24(2022-01-25)
|
||||
# uView2.0重磅发布,利剑出鞘,一统江湖
|
||||
|
||||
1. 修复swiper在current指定非0时缩放有误
|
||||
2. 修复u-icon添加stop属性的时候报错
|
||||
3. 优化遗留的通过正则判断rpx单位的问题
|
||||
4. 优化Layout布局 vue使用gutter时,会超出固定区域
|
||||
5. 优化search组件高度单位问题(rpx -> px)
|
||||
6. 修复u-image slot 加载和错误的图片失去了高度
|
||||
7. 修复u-index-list中footer插槽与header插槽存在性判断错误
|
||||
8. 修复部分机型下u-popup关闭时会闪烁
|
||||
9. 修复u-image在nvue-app下失去宽高
|
||||
10. 修复u-popup运行报错
|
||||
11. 修复u-tooltip报错
|
||||
12. 修复box-sizing在app下的警告
|
||||
13. 修复u-navbar在小程序中报运行时错误
|
||||
14. 其他修复
|
||||
## 2.0.23(2022-01-24)
|
||||
# uView2.0重磅发布,利剑出鞘,一统江湖
|
||||
|
||||
1. 修复image组件在hx3.3.9的nvue下可能会显示异常的问题
|
||||
2. 修复col组件gutter参数带rpx单位处理不正确的问题
|
||||
3. 修复text组件单行时无法显示省略号的问题
|
||||
4. navbar添加titleStyle参数
|
||||
5. 升级到hx3.3.9可消除nvue下控制台样式警告的问题
|
||||
## 2.0.22(2022-01-19)
|
||||
# uView2.0重磅发布,利剑出鞘,一统江湖
|
||||
|
||||
1. $u.page()方法优化,避免在特殊场景可能报错的问题
|
||||
2. picker组件添加immediateChange参数
|
||||
3. 新增$u.pages()方法
|
||||
## 2.0.21(2022-01-19)
|
||||
# uView2.0重磅发布,利剑出鞘,一统江湖
|
||||
|
||||
1. 优化:form组件在用户设置rules的时候提示用户model必传
|
||||
2. 优化遗留的通过正则判断rpx单位的问题
|
||||
3. 修复微信小程序环境中tabbar组件开启safeAreaInsetBottom属性后,placeholder高度填充不正确
|
||||
4. 修复swiper在current指定非0时缩放有误
|
||||
5. 修复u-icon添加stop属性的时候报错
|
||||
6. 修复upload组件在accept=all的时候没有作用
|
||||
7. 修复在text组件mode为phone时call属性无效的问题
|
||||
8. 处理u-form clearValidate方法
|
||||
9. 其他修复
|
||||
## 2.0.20(2022-01-14)
|
||||
# uView2.0重磅发布,利剑出鞘,一统江湖
|
||||
|
||||
1. 修复calendar默认会选择一个日期,如果直接点确定的话,无法取到值的问题
|
||||
2. 修复Slider缺少disabled props 还有注释
|
||||
3. 修复u-notice-bar点击事件无法拿到index索引值的问题
|
||||
4. 修复u-collapse-item在vue文件下,app端自定义插槽不生效的问题
|
||||
5. 优化头像为空时显示默认头像
|
||||
6. 修复图片地址赋值后判断加载状态为完成问题
|
||||
7. 修复日历滚动到默认日期月份区域
|
||||
8. search组件暴露点击左边icon事件
|
||||
9. 修复u-form clearValidate方法不生效
|
||||
10. upload h5端增加返回文件参数(文件的name参数)
|
||||
11. 处理upload选择文件后url为blob类型无法预览的问题
|
||||
12. u-code-input 修复输入框没有往左移出一半屏幕
|
||||
13. 修复Upload上传 disabled为true时,控制台报hoverClass类型错误
|
||||
14. 临时处理ios app下grid点击坍塌问题
|
||||
15. 其他修复
|
||||
## 2.0.19(2021-12-29)
|
||||
# uView2.0重磅发布,利剑出鞘,一统江湖
|
||||
|
||||
1. 优化微信小程序包体积可在微信中预览,请升级HbuilderX3.3.4,同时在“运行->运行到小程序模拟器”中勾选“运行时是否压缩代码”
|
||||
2. 优化微信小程序setData性能,处理某些方法如$u.route()无法在模板中使用的问题
|
||||
3. navbar添加autoBack参数
|
||||
4. 允许avatar组件的事件冒泡
|
||||
5. 修复cell组件报错问题
|
||||
6. 其他修复
|
||||
## 2.0.18(2021-12-28)
|
||||
# uView2.0重磅发布,利剑出鞘,一统江湖
|
||||
|
||||
1. 修复app端编译报错问题
|
||||
2. 重新处理微信小程序端setData过大的性能问题
|
||||
3. 修复边框问题
|
||||
4. 修复最大最小月份不大于0则没有数据出现的问题
|
||||
5. 修复SwipeAction微信小程序端无法上下滑动问题
|
||||
6. 修复input的placeholder在小程序端默认显示为true问题
|
||||
7. 修复divider组件click事件无效问题
|
||||
8. 修复u-code-input maxlength 属性值为 String 类型时显示异常
|
||||
9. 修复当 grid只有 1到2时 在小程序端algin设置无效的问题
|
||||
10. 处理form-item的label为top时,取消错误提示的左边距
|
||||
11. 其他修复
|
||||
## 2.0.17(2021-12-26)
|
||||
## uView正在参与开源中国的“年度最佳项目”评选,之前投过票的现在也可以投票,恳请同学们投一票,[点此帮助uView](https://www.oschina.net/project/top_cn_2021/?id=583)
|
||||
|
||||
# uView2.0重磅发布,利剑出鞘,一统江湖
|
||||
|
||||
1. 解决HBuilderX3.3.3.20211225版本导致的样式问题
|
||||
2. calendar日历添加monthNum参数
|
||||
3. navbar添加center slot
|
||||
## 2.0.16(2021-12-25)
|
||||
## uView正在参与开源中国的“年度最佳项目”评选,之前投过票的现在也可以投票,恳请同学们投一票,[点此帮助uView](https://www.oschina.net/project/top_cn_2021/?id=583)
|
||||
|
||||
# uView2.0重磅发布,利剑出鞘,一统江湖
|
||||
|
||||
1. 解决微信小程序setData性能问题
|
||||
2. 修复count-down组件change事件不触发问题
|
||||
## 2.0.15(2021-12-21)
|
||||
## uView正在参与开源中国的“年度最佳项目”评选,之前投过票的现在也可以投票,恳请同学们投一票,[点此帮助uView](https://www.oschina.net/project/top_cn_2021/?id=583)
|
||||
|
||||
# uView2.0重磅发布,利剑出鞘,一统江湖
|
||||
|
||||
1. 修复Cell单元格titleWidth无效
|
||||
2. 修复cheakbox组件ischecked不更新
|
||||
3. 修复keyboard是否显示"."按键默认值问题
|
||||
4. 修复number-keyboard是否显示键盘的"."符号问题
|
||||
5. 修复Input输入框 readonly无效
|
||||
6. 修复u-avatar 导致打包app、H5时候报错问题
|
||||
7. 修复Upload上传deletable无效
|
||||
8. 修复upload当设置maxSize时无效的问题
|
||||
9. 修复tabs lineWidth传入带单位的字符串的时候偏移量计算错误问题
|
||||
10. 修复rate组件在有padding的view内,显示的星星位置和可触摸区域不匹配,无法正常选中星星
|
||||
## 2.0.13(2021-12-14)
|
||||
## [点击加群交流反馈:364463526](https://jq.qq.com/?_chanwv=1027&k=mCxS3TGY)
|
||||
|
||||
# uView2.0重磅发布,利剑出鞘,一统江湖
|
||||
|
||||
1. 修复配置默认单位为rpx可能会导致自定义导航栏高度异常的问题
|
||||
## 2.0.12(2021-12-14)
|
||||
## [点击加群交流反馈:364463526](https://jq.qq.com/?_chanwv=1027&k=mCxS3TGY)
|
||||
|
||||
# uView2.0重磅发布,利剑出鞘,一统江湖
|
||||
|
||||
1. 修复tabs组件在vue环境下划线消失的问题
|
||||
2. 修复upload组件在安卓小程序无法选择视频的问题
|
||||
3. 添加uni.$u.config.unit配置,用于配置参数默认单位,详见:[默认单位配置](https://www.uviewui.com/components/setting.html#%E9%BB%98%E8%AE%A4%E5%8D%95%E4%BD%8D%E9%85%8D%E7%BD%AE)
|
||||
4. 修复textarea组件在没绑定v-model时,字符统计不生效问题
|
||||
5. 修复nvue下控制是否出现滚动条失效问题
|
||||
## 2.0.11(2021-12-13)
|
||||
## [点击加群交流反馈:364463526](https://jq.qq.com/?_chanwv=1027&k=mCxS3TGY)
|
||||
|
||||
# uView2.0重磅发布,利剑出鞘,一统江湖
|
||||
|
||||
1. text组件align参数无效的问题
|
||||
2. subsection组件添加keyName参数
|
||||
3. upload组件无法判断[Object file]类型的问题
|
||||
4. 处理notify层级过低问题
|
||||
5. codeInput组件添加disabledDot参数
|
||||
6. 处理actionSheet组件round参数无效的问题
|
||||
7. calendar组件添加round参数用于控制圆角值
|
||||
8. 处理swipeAction组件在vue环境下默认被打开的问题
|
||||
9. button组件的throttleTime节流参数无效的问题
|
||||
10. 解决u-notify手动关闭方法close()无效的问题
|
||||
11. input组件readonly不生效问题
|
||||
12. tag组件type参数为info不生效问题
|
||||
## 2.0.10(2021-12-08)
|
||||
## [点击加群交流反馈:364463526](https://jq.qq.com/?_chanwv=1027&k=mCxS3TGY)
|
||||
|
||||
# uView2.0重磅发布,利剑出鞘,一统江湖
|
||||
|
||||
1. 修复button sendMessagePath属性不生效
|
||||
2. 修复DatetimePicker选择器title无效
|
||||
3. 修复u-toast设置loading=true不生效
|
||||
4. 修复u-text金额模式传0报错
|
||||
5. 修复u-toast组件的icon属性配置不生效
|
||||
6. button的icon在特殊场景下的颜色优化
|
||||
7. IndexList优化,增加#
|
||||
## 2.0.9(2021-12-01)
|
||||
## [点击加群交流反馈:232041042](https://jq.qq.com/?_wv=1027&k=KnbeceDU)
|
||||
|
||||
# uView2.0重磅发布,利剑出鞘,一统江湖
|
||||
|
||||
1. 优化swiper的height支持100%值(仅vue有效),修复嵌入视频时click事件无法触发的问题
|
||||
2. 优化tabs组件对list值为空的判断,或者动态变化list时重新计算相关尺寸的问题
|
||||
3. 优化datetime-picker组件逻辑,让其后续打开的默认值为上一次的选中值,需要通过v-model绑定值才有效
|
||||
4. 修复upload内嵌在其他组件中,选择图片可能不会换行的问题
|
||||
## 2.0.8(2021-12-01)
|
||||
## [点击加群交流反馈:232041042](https://jq.qq.com/?_wv=1027&k=KnbeceDU)
|
||||
|
||||
# uView2.0重磅发布,利剑出鞘,一统江湖
|
||||
|
||||
1. 修复toast的position参数无效问题
|
||||
2. 处理input在ios nvue上无法获得焦点的问题
|
||||
3. avatar-group组件添加extraValue参数,让剩余展示数量可手动控制
|
||||
4. tabs组件添加keyName参数用于配置从对象中读取的键名
|
||||
5. 处理text组件名字脱敏默认配置无效的问题
|
||||
6. 处理picker组件item文本太长换行问题
|
||||
## 2.0.7(2021-11-30)
|
||||
## [点击加群交流反馈:232041042](https://jq.qq.com/?_wv=1027&k=KnbeceDU)
|
||||
|
||||
# uView2.0重磅发布,利剑出鞘,一统江湖
|
||||
|
||||
1. 修复radio和checkbox动态改变v-model无效的问题。
|
||||
2. 优化form规则validator在微信小程序用法
|
||||
3. 修复backtop组件mode参数在微信小程序无效的问题
|
||||
4. 处理Album的previewFullImage属性无效的问题
|
||||
5. 处理u-datetime-picker组件mode='time'在选择改变时间时,控制台报错的问题
|
||||
## 2.0.6(2021-11-27)
|
||||
## [点击加群交流反馈:232041042](https://jq.qq.com/?_wv=1027&k=KnbeceDU)
|
||||
|
||||
# uView2.0重磅发布,利剑出鞘,一统江湖
|
||||
|
||||
1. 处理tag组件在vue下边框无效的问题。
|
||||
2. 处理popup组件圆角参数可能无效的问题。
|
||||
3. 处理tabs组件lineColor参数可能无效的问题。
|
||||
4. propgress组件在值很小时,显示异常的问题。
|
||||
## 2.0.5(2021-11-25)
|
||||
## [点击加群交流反馈:232041042](https://jq.qq.com/?_wv=1027&k=KnbeceDU)
|
||||
|
||||
# uView2.0重磅发布,利剑出鞘,一统江湖
|
||||
|
||||
1. calendar在vue下显示异常问题。
|
||||
2. form组件labelPosition和errorType参数无效的问题
|
||||
3. input组件inputAlign无效的问题
|
||||
4. 其他一些修复
|
||||
## 2.0.4(2021-11-23)
|
||||
## [点击加群交流反馈:232041042](https://jq.qq.com/?_wv=1027&k=KnbeceDU)
|
||||
|
||||
# uView2.0重磅发布,利剑出鞘,一统江湖
|
||||
|
||||
0. input组件缺失@confirm事件,以及subfix和prefix无效问题
|
||||
1. component.scss文件样式在vue下干扰全局布局问题
|
||||
2. 修复subsection在vue环境下表现异常的问题
|
||||
3. tag组件的bgColor等参数无效的问题
|
||||
4. upload组件不换行的问题
|
||||
5. 其他的一些修复处理
|
||||
## 2.0.3(2021-11-16)
|
||||
## [点击加群交流反馈:1129077272](https://jq.qq.com/?_wv=1027&k=KnbeceDU)
|
||||
|
||||
# uView2.0重磅发布,利剑出鞘,一统江湖
|
||||
|
||||
1. uView2.0已实现全面兼容nvue
|
||||
2. uView2.0对1.x进行了架构重构,细节和性能都有极大提升
|
||||
3. 目前uView2.0为公测阶段,相关细节可能会有变动
|
||||
4. 我们写了一份与1.x的对比指南,详见[对比1.x](https://www.uviewui.com/components/diff1.x.html)
|
||||
5. 处理modal的confirm回调事件拼写错误问题
|
||||
6. 处理input组件@input事件参数错误问题
|
||||
7. 其他一些修复
|
||||
## 2.0.2(2021-11-16)
|
||||
## [点击加群交流反馈:1129077272](https://jq.qq.com/?_wv=1027&k=KnbeceDU)
|
||||
|
||||
# uView2.0重磅发布,利剑出鞘,一统江湖
|
||||
|
||||
1. uView2.0已实现全面兼容nvue
|
||||
2. uView2.0对1.x进行了架构重构,细节和性能都有极大提升
|
||||
3. 目前uView2.0为公测阶段,相关细节可能会有变动
|
||||
4. 我们写了一份与1.x的对比指南,详见[对比1.x](https://www.uviewui.com/components/diff1.x.html)
|
||||
5. 修复input组件formatter参数缺失问题
|
||||
6. 优化loading-icon组件的scss写法问题,防止不兼容新版本scss
|
||||
## 2.0.0(2020-11-15)
|
||||
## [点击加群交流反馈:1129077272](https://jq.qq.com/?_wv=1027&k=KnbeceDU)
|
||||
|
||||
# uView2.0重磅发布,利剑出鞘,一统江湖
|
||||
|
||||
1. uView2.0已实现全面兼容nvue
|
||||
2. uView2.0对1.x进行了架构重构,细节和性能都有极大提升
|
||||
3. 目前uView2.0为公测阶段,相关细节可能会有变动
|
||||
4. 我们写了一份与1.x的对比指南,详见[对比1.x](https://www.uviewui.com/components/diff1.x.html)
|
||||
5. 修复input组件formatter参数缺失问题
|
||||
|
||||
|
|
@ -1,78 +0,0 @@
|
|||
<template>
|
||||
<uvForm
|
||||
ref="uForm"
|
||||
:model="model"
|
||||
:rules="rules"
|
||||
:errorType="errorType"
|
||||
:borderBottom="borderBottom"
|
||||
:labelPosition="labelPosition"
|
||||
:labelWidth="labelWidth"
|
||||
:labelAlign="labelAlign"
|
||||
:labelStyle="labelStyle"
|
||||
:customStyle="customStyle"
|
||||
>
|
||||
<slot />
|
||||
</uvForm>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* 此组件存在的理由是,在nvue下,u-form被uni-app官方占用了,u-form在nvue中相当于form组件
|
||||
* 所以在nvue下,取名为u--form,内部其实还是u-form.vue,只不过做一层中转
|
||||
*/
|
||||
import uvForm from '../u-form/u-form.vue';
|
||||
import props from '../u-form/props.js'
|
||||
export default {
|
||||
// #ifdef MP-WEIXIN
|
||||
name: 'u-form',
|
||||
// #endif
|
||||
// #ifndef MP-WEIXIN
|
||||
name: 'u--form',
|
||||
// #endif
|
||||
mixins: [uni.$u.mpMixin, props, uni.$u.mixin],
|
||||
components: {
|
||||
uvForm
|
||||
},
|
||||
created() {
|
||||
this.children = []
|
||||
},
|
||||
methods: {
|
||||
// 手动设置校验的规则,如果规则中有函数的话,微信小程序中会过滤掉,所以只能手动调用设置规则
|
||||
setRules(rules) {
|
||||
this.$refs.uForm.setRules(rules)
|
||||
},
|
||||
validate() {
|
||||
/**
|
||||
* 在微信小程序中,通过this.$parent拿到的父组件是u--form,而不是其内嵌的u-form
|
||||
* 导致在u-form组件中,拿不到对应的children数组,从而校验无效,所以这里每次调用u-form组件中的
|
||||
* 对应方法的时候,在小程序中都先将u--form的children赋值给u-form中的children
|
||||
*/
|
||||
// #ifdef MP-WEIXIN
|
||||
this.setMpData()
|
||||
// #endif
|
||||
return this.$refs.uForm.validate()
|
||||
},
|
||||
validateField(value, callback) {
|
||||
// #ifdef MP-WEIXIN
|
||||
this.setMpData()
|
||||
// #endif
|
||||
return this.$refs.uForm.validateField(value, callback)
|
||||
},
|
||||
resetFields() {
|
||||
// #ifdef MP-WEIXIN
|
||||
this.setMpData()
|
||||
// #endif
|
||||
return this.$refs.uForm.resetFields()
|
||||
},
|
||||
clearValidate(props) {
|
||||
// #ifdef MP-WEIXIN
|
||||
this.setMpData()
|
||||
// #endif
|
||||
return this.$refs.uForm.clearValidate(props)
|
||||
},
|
||||
setMpData() {
|
||||
this.$refs.uForm.children = this.children
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
|
@ -1,47 +0,0 @@
|
|||
<template>
|
||||
<uvImage
|
||||
:src="src"
|
||||
:mode="mode"
|
||||
:width="width"
|
||||
:height="height"
|
||||
:shape="shape"
|
||||
:radius="radius"
|
||||
:lazyLoad="lazyLoad"
|
||||
:showMenuByLongpress="showMenuByLongpress"
|
||||
:loadingIcon="loadingIcon"
|
||||
:errorIcon="errorIcon"
|
||||
:showLoading="showLoading"
|
||||
:showError="showError"
|
||||
:fade="fade"
|
||||
:webp="webp"
|
||||
:duration="duration"
|
||||
:bgColor="bgColor"
|
||||
:customStyle="customStyle"
|
||||
@click="$emit('click')"
|
||||
@error="$emit('error')"
|
||||
@load="$emit('load')"
|
||||
>
|
||||
<template v-slot:loading>
|
||||
<slot name="loading"></slot>
|
||||
</template>
|
||||
<template v-slot:error>
|
||||
<slot name="error"></slot>
|
||||
</template>
|
||||
</uvImage>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* 此组件存在的理由是,在nvue下,u-image被uni-app官方占用了,u-image在nvue中相当于image组件
|
||||
* 所以在nvue下,取名为u--image,内部其实还是u-iamge.vue,只不过做一层中转
|
||||
*/
|
||||
import uvImage from '../u-image/u-image.vue';
|
||||
import props from '../u-image/props.js';
|
||||
export default {
|
||||
name: 'u--image',
|
||||
mixins: [uni.$u.mpMixin, props, uni.$u.mixin],
|
||||
components: {
|
||||
uvImage
|
||||
},
|
||||
}
|
||||
</script>
|
|
@ -1,72 +0,0 @@
|
|||
<template>
|
||||
<uvInput
|
||||
:value="value"
|
||||
:type="type"
|
||||
:fixed="fixed"
|
||||
:disabled="disabled"
|
||||
:disabledColor="disabledColor"
|
||||
:clearable="clearable"
|
||||
:password="password"
|
||||
:maxlength="maxlength"
|
||||
:placeholder="placeholder"
|
||||
:placeholderClass="placeholderClass"
|
||||
:placeholderStyle="placeholderStyle"
|
||||
:showWordLimit="showWordLimit"
|
||||
:confirmType="confirmType"
|
||||
:confirmHold="confirmHold"
|
||||
:holdKeyboard="holdKeyboard"
|
||||
:focus="focus"
|
||||
:autoBlur="autoBlur"
|
||||
:disableDefaultPadding="disableDefaultPadding"
|
||||
:cursor="cursor"
|
||||
:cursorSpacing="cursorSpacing"
|
||||
:selectionStart="selectionStart"
|
||||
:selectionEnd="selectionEnd"
|
||||
:adjustPosition="adjustPosition"
|
||||
:inputAlign="inputAlign"
|
||||
:fontSize="fontSize"
|
||||
:color="color"
|
||||
:prefixIcon="prefixIcon"
|
||||
:suffixIcon="suffixIcon"
|
||||
:suffixIconStyle="suffixIconStyle"
|
||||
:prefixIconStyle="prefixIconStyle"
|
||||
:border="border"
|
||||
:readonly="readonly"
|
||||
:shape="shape"
|
||||
:customStyle="customStyle"
|
||||
:formatter="formatter"
|
||||
@focus="$emit('focus')"
|
||||
@blur="$emit('blur')"
|
||||
@keyboardheightchange="$emit('keyboardheightchange')"
|
||||
@change="e => $emit('change', e)"
|
||||
@input="e => $emit('input', e)"
|
||||
@confirm="e => $emit('confirm', e)"
|
||||
@clear="$emit('clear')"
|
||||
@click="$emit('click')"
|
||||
>
|
||||
<!-- #ifdef MP -->
|
||||
<slot name="prefix"></slot>
|
||||
<slot name="suffix"></slot>
|
||||
<!-- #endif -->
|
||||
<!-- #ifndef MP -->
|
||||
<slot name="prefix" slot="prefix"></slot>
|
||||
<slot name="suffix" slot="suffix"></slot>
|
||||
<!-- #endif -->
|
||||
</uvInput>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* 此组件存在的理由是,在nvue下,u-input被uni-app官方占用了,u-input在nvue中相当于input组件
|
||||
* 所以在nvue下,取名为u--input,内部其实还是u-input.vue,只不过做一层中转
|
||||
*/
|
||||
import uvInput from '../u-input/u-input.vue';
|
||||
import props from '../u-input/props.js'
|
||||
export default {
|
||||
name: 'u--input',
|
||||
mixins: [uni.$u.mpMixin, props, uni.$u.mixin],
|
||||
components: {
|
||||
uvInput
|
||||
},
|
||||
}
|
||||
</script>
|
|
@ -1,44 +0,0 @@
|
|||
<template>
|
||||
<uvText
|
||||
:type="type"
|
||||
:show="show"
|
||||
:text="text"
|
||||
:prefixIcon="prefixIcon"
|
||||
:suffixIcon="suffixIcon"
|
||||
:mode="mode"
|
||||
:href="href"
|
||||
:format="format"
|
||||
:call="call"
|
||||
:openType="openType"
|
||||
:bold="bold"
|
||||
:block="block"
|
||||
:lines="lines"
|
||||
:color="color"
|
||||
:decoration="decoration"
|
||||
:size="size"
|
||||
:iconStyle="iconStyle"
|
||||
:margin="margin"
|
||||
:lineHeight="lineHeight"
|
||||
:align="align"
|
||||
:wordWrap="wordWrap"
|
||||
:customStyle="customStyle"
|
||||
@click="$emit('click')"
|
||||
></uvText>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* 此组件存在的理由是,在nvue下,u-text被uni-app官方占用了,u-text在nvue中相当于input组件
|
||||
* 所以在nvue下,取名为u--input,内部其实还是u-text.vue,只不过做一层中转
|
||||
* 不使用v-bind="$attrs",而是分开独立写传参,是因为微信小程序不支持此写法
|
||||
*/
|
||||
import uvText from "../u-text/u-text.vue";
|
||||
import props from "../u-text/props.js";
|
||||
export default {
|
||||
name: "u--text",
|
||||
mixins: [uni.$u.mpMixin, props, uni.$u.mixin],
|
||||
components: {
|
||||
uvText,
|
||||
},
|
||||
};
|
||||
</script>
|
|
@ -1,47 +0,0 @@
|
|||
<template>
|
||||
<uvTextarea
|
||||
:value="value"
|
||||
:placeholder="placeholder"
|
||||
:height="height"
|
||||
:confirmType="confirmType"
|
||||
:disabled="disabled"
|
||||
:count="count"
|
||||
:focus="focus"
|
||||
:autoHeight="autoHeight"
|
||||
:fixed="fixed"
|
||||
:cursorSpacing="cursorSpacing"
|
||||
:cursor="cursor"
|
||||
:showConfirmBar="showConfirmBar"
|
||||
:selectionStart="selectionStart"
|
||||
:selectionEnd="selectionEnd"
|
||||
:adjustPosition="adjustPosition"
|
||||
:disableDefaultPadding="disableDefaultPadding"
|
||||
:holdKeyboard="holdKeyboard"
|
||||
:maxlength="maxlength"
|
||||
:border="border"
|
||||
:customStyle="customStyle"
|
||||
:formatter="formatter"
|
||||
@focus="e => $emit('focus')"
|
||||
@blur="e => $emit('blur')"
|
||||
@linechange="e => $emit('linechange', e)"
|
||||
@confirm="e => $emit('confirm')"
|
||||
@input="e => $emit('input', e)"
|
||||
@keyboardheightchange="e => $emit('keyboardheightchange')"
|
||||
></uvTextarea>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* 此组件存在的理由是,在nvue下,u--textarea被uni-app官方占用了,u-textarea在nvue中相当于textarea组件
|
||||
* 所以在nvue下,取名为u--textarea,内部其实还是u-textarea.vue,只不过做一层中转
|
||||
*/
|
||||
import uvTextarea from '../u-textarea/u-textarea.vue';
|
||||
import props from '../u-textarea/props.js'
|
||||
export default {
|
||||
name: 'u--textarea',
|
||||
mixins: [uni.$u.mpMixin, props, uni.$u.mixin],
|
||||
components: {
|
||||
uvTextarea
|
||||
},
|
||||
}
|
||||
</script>
|
|
@ -1,54 +0,0 @@
|
|||
export default {
|
||||
props: {
|
||||
// 操作菜单是否展示 (默认false)
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.actionSheet.show
|
||||
},
|
||||
// 标题
|
||||
title: {
|
||||
type: String,
|
||||
default: uni.$u.props.actionSheet.title
|
||||
},
|
||||
// 选项上方的描述信息
|
||||
description: {
|
||||
type: String,
|
||||
default: uni.$u.props.actionSheet.description
|
||||
},
|
||||
// 数据
|
||||
actions: {
|
||||
type: Array,
|
||||
default: uni.$u.props.actionSheet.actions
|
||||
},
|
||||
// 取消按钮的文字,不为空时显示按钮
|
||||
cancelText: {
|
||||
type: String,
|
||||
default: uni.$u.props.actionSheet.cancelText
|
||||
},
|
||||
// 点击某个菜单项时是否关闭弹窗
|
||||
closeOnClickAction: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.actionSheet.closeOnClickAction
|
||||
},
|
||||
// 处理底部安全区(默认true)
|
||||
safeAreaInsetBottom: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.actionSheet.safeAreaInsetBottom
|
||||
},
|
||||
// 小程序的打开方式
|
||||
openType: {
|
||||
type: String,
|
||||
default: uni.$u.props.actionSheet.openType
|
||||
},
|
||||
// 点击遮罩是否允许关闭 (默认true)
|
||||
closeOnClickOverlay: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.actionSheet.closeOnClickOverlay
|
||||
},
|
||||
// 圆角值
|
||||
round: {
|
||||
type: [Boolean, String, Number],
|
||||
default: uni.$u.props.actionSheet.round
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,275 +0,0 @@
|
|||
|
||||
<template>
|
||||
<u-popup
|
||||
:show="show"
|
||||
mode="bottom"
|
||||
@close="close"
|
||||
:closeOnClickOverlay="closeOnClickOverlay"
|
||||
:safeAreaInsetBottom="safeAreaInsetBottom"
|
||||
:round="round"
|
||||
>
|
||||
<view class="u-action-sheet">
|
||||
<view
|
||||
class="u-action-sheet__header"
|
||||
v-if="title"
|
||||
>
|
||||
<text class="u-action-sheet__header__title u-line-1">{{title}}</text>
|
||||
<view
|
||||
class="u-action-sheet__header__icon-wrap"
|
||||
@tap.stop="close"
|
||||
>
|
||||
<u-icon
|
||||
name="close"
|
||||
size="17"
|
||||
color="#c8c9cc"
|
||||
bold
|
||||
></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
<text
|
||||
class="u-action-sheet__description"
|
||||
:style="[{
|
||||
marginTop: `${title && description ? 0 : '18px'}`
|
||||
}]"
|
||||
v-if="description"
|
||||
>{{description}}</text>
|
||||
<slot>
|
||||
<u-line v-if="description"></u-line>
|
||||
<view class="u-action-sheet__item-wrap">
|
||||
<template v-for="(item, index) in actions">
|
||||
<!-- #ifdef MP -->
|
||||
<button
|
||||
:key="index"
|
||||
class="u-reset-button"
|
||||
:openType="item.openType"
|
||||
@getuserinfo="onGetUserInfo"
|
||||
@contact="onContact"
|
||||
@getphonenumber="onGetPhoneNumber"
|
||||
@error="onError"
|
||||
@launchapp="onLaunchApp"
|
||||
@opensetting="onOpenSetting"
|
||||
:lang="lang"
|
||||
:session-from="sessionFrom"
|
||||
:send-message-title="sendMessageTitle"
|
||||
:send-message-path="sendMessagePath"
|
||||
:send-message-img="sendMessageImg"
|
||||
:show-message-card="showMessageCard"
|
||||
:app-parameter="appParameter"
|
||||
@tap="selectHandler(index)"
|
||||
:hover-class="!item.disabled && !item.loading ? 'u-action-sheet--hover' : ''"
|
||||
>
|
||||
<!-- #endif -->
|
||||
<view
|
||||
class="u-action-sheet__item-wrap__item"
|
||||
@tap.stop="selectHandler(index)"
|
||||
:hover-class="!item.disabled && !item.loading ? 'u-action-sheet--hover' : ''"
|
||||
:hover-stay-time="150"
|
||||
>
|
||||
<template v-if="!item.loading">
|
||||
<text
|
||||
class="u-action-sheet__item-wrap__item__name"
|
||||
:style="[itemStyle(index)]"
|
||||
>{{ item.name }}</text>
|
||||
<text
|
||||
v-if="item.subname"
|
||||
class="u-action-sheet__item-wrap__item__subname"
|
||||
>{{ item.subname }}</text>
|
||||
</template>
|
||||
<u-loading-icon
|
||||
v-else
|
||||
custom-class="van-action-sheet__loading"
|
||||
size="18"
|
||||
mode="circle"
|
||||
/>
|
||||
</view>
|
||||
<!-- #ifdef MP -->
|
||||
</button>
|
||||
<!-- #endif -->
|
||||
<u-line v-if="index !== actions.length - 1"></u-line>
|
||||
</template>
|
||||
</view>
|
||||
</slot>
|
||||
<u-gap
|
||||
bgColor="#eaeaec"
|
||||
height="6"
|
||||
v-if="cancelText"
|
||||
></u-gap>
|
||||
<view hover-class="u-action-sheet--hover">
|
||||
<text
|
||||
@touchmove.stop.prevent
|
||||
:hover-stay-time="150"
|
||||
v-if="cancelText"
|
||||
class="u-action-sheet__cancel-text"
|
||||
@tap="close"
|
||||
>{{cancelText}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</u-popup>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import openType from '../../libs/mixin/openType'
|
||||
import button from '../../libs/mixin/button'
|
||||
import props from './props.js';
|
||||
/**
|
||||
* ActionSheet 操作菜单
|
||||
* @description 本组件用于从底部弹出一个操作菜单,供用户选择并返回结果。本组件功能类似于uni的uni.showActionSheetAPI,配置更加灵活,所有平台都表现一致。
|
||||
* @tutorial https://www.uviewui.com/components/actionSheet.html
|
||||
*
|
||||
* @property {Boolean} show 操作菜单是否展示 (默认 false )
|
||||
* @property {String} title 操作菜单标题
|
||||
* @property {String} description 选项上方的描述信息
|
||||
* @property {Array<Object>} actions 按钮的文字数组,见官方文档示例
|
||||
* @property {String} cancelText 取消按钮的提示文字,不为空时显示按钮
|
||||
* @property {Boolean} closeOnClickAction 点击某个菜单项时是否关闭弹窗 (默认 true )
|
||||
* @property {Boolean} safeAreaInsetBottom 处理底部安全区 (默认 true )
|
||||
* @property {String} openType 小程序的打开方式 (contact | launchApp | getUserInfo | openSetting |getPhoneNumber |error )
|
||||
* @property {Boolean} closeOnClickOverlay 点击遮罩是否允许关闭 (默认 true )
|
||||
* @property {Number|String} round 圆角值,默认无圆角 (默认 0 )
|
||||
* @property {String} lang 指定返回用户信息的语言,zh_CN 简体中文,zh_TW 繁体中文,en 英文
|
||||
* @property {String} sessionFrom 会话来源,openType="contact"时有效
|
||||
* @property {String} sendMessageTitle 会话内消息卡片标题,openType="contact"时有效
|
||||
* @property {String} sendMessagePath 会话内消息卡片点击跳转小程序路径,openType="contact"时有效
|
||||
* @property {String} sendMessageImg 会话内消息卡片图片,openType="contact"时有效
|
||||
* @property {Boolean} showMessageCard 是否显示会话内消息卡片,设置此参数为 true,用户进入客服会话会在右下角显示"可能要发送的小程序"提示,用户点击后可以快速发送小程序消息,openType="contact"时有效 (默认 false )
|
||||
* @property {String} appParameter 打开 APP 时,向 APP 传递的参数,openType=launchApp 时有效
|
||||
*
|
||||
* @event {Function} select 点击ActionSheet列表项时触发
|
||||
* @event {Function} close 点击取消按钮时触发
|
||||
* @event {Function} getuserinfo 用户点击该按钮时,会返回获取到的用户信息,回调的 detail 数据与 wx.getUserInfo 返回的一致,openType="getUserInfo"时有效
|
||||
* @event {Function} contact 客服消息回调,openType="contact"时有效
|
||||
* @event {Function} getphonenumber 获取用户手机号回调,openType="getPhoneNumber"时有效
|
||||
* @event {Function} error 当使用开放能力时,发生错误的回调,openType="error"时有效
|
||||
* @event {Function} launchapp 打开 APP 成功的回调,openType="launchApp"时有效
|
||||
* @event {Function} opensetting 在打开授权设置页后回调,openType="openSetting"时有效
|
||||
* @example <u-action-sheet :actions="list" :title="title" :show="show"></u-action-sheet>
|
||||
*/
|
||||
export default {
|
||||
name: "u-action-sheet",
|
||||
// 一些props参数和methods方法,通过mixin混入,因为其他文件也会用到
|
||||
mixins: [openType, button, uni.$u.mixin, props],
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 操作项目的样式
|
||||
itemStyle() {
|
||||
return (index) => {
|
||||
let style = {};
|
||||
if (this.actions[index].color) style.color = this.actions[index].color
|
||||
if (this.actions[index].fontSize) style.fontSize = uni.$u.addUnit(this.actions[index].fontSize)
|
||||
// 选项被禁用的样式
|
||||
if (this.actions[index].disabled) style.color = '#c0c4cc'
|
||||
return style;
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
close() {
|
||||
// 允许点击遮罩关闭时,才发出close事件
|
||||
if(this.closeOnClickOverlay) {
|
||||
this.$emit('close')
|
||||
}
|
||||
},
|
||||
selectHandler(index) {
|
||||
const item = this.actions[index]
|
||||
if (item && !item.disabled && !item.loading) {
|
||||
this.$emit('select', item)
|
||||
if (this.closeOnClickAction) {
|
||||
this.$emit('close')
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/components.scss";
|
||||
$u-action-sheet-reset-button-width:100% !default;
|
||||
$u-action-sheet-title-font-size: 16px !default;
|
||||
$u-action-sheet-title-padding: 12px 30px !default;
|
||||
$u-action-sheet-title-color: $u-main-color !default;
|
||||
$u-action-sheet-header-icon-wrap-right:15px !default;
|
||||
$u-action-sheet-header-icon-wrap-top:15px !default;
|
||||
$u-action-sheet-description-font-size:13px !default;
|
||||
$u-action-sheet-description-color:14px !default;
|
||||
$u-action-sheet-description-margin: 18px 15px !default;
|
||||
$u-action-sheet-item-wrap-item-padding:15px !default;
|
||||
$u-action-sheet-item-wrap-name-font-size:16px !default;
|
||||
$u-action-sheet-item-wrap-subname-font-size:13px !default;
|
||||
$u-action-sheet-item-wrap-subname-color: #c0c4cc !default;
|
||||
$u-action-sheet-item-wrap-subname-margin-top:10px !default;
|
||||
$u-action-sheet-cancel-text-font-size:16px !default;
|
||||
$u-action-sheet-cancel-text-color:$u-content-color !default;
|
||||
$u-action-sheet-cancel-text-font-size:15px !default;
|
||||
$u-action-sheet-cancel-text-hover-background-color:rgb(242, 243, 245) !default;
|
||||
|
||||
.u-reset-button {
|
||||
width: $u-action-sheet-reset-button-width;
|
||||
}
|
||||
|
||||
.u-action-sheet {
|
||||
text-align: center;
|
||||
&__header {
|
||||
position: relative;
|
||||
padding: $u-action-sheet-title-padding;
|
||||
&__title {
|
||||
font-size: $u-action-sheet-title-font-size;
|
||||
color: $u-action-sheet-title-color;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&__icon-wrap {
|
||||
position: absolute;
|
||||
right: $u-action-sheet-header-icon-wrap-right;
|
||||
top: $u-action-sheet-header-icon-wrap-top;
|
||||
}
|
||||
}
|
||||
|
||||
&__description {
|
||||
font-size: $u-action-sheet-description-font-size;
|
||||
color: $u-tips-color;
|
||||
margin: $u-action-sheet-description-margin;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&__item-wrap {
|
||||
|
||||
&__item {
|
||||
padding: $u-action-sheet-item-wrap-item-padding;
|
||||
@include flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
|
||||
&__name {
|
||||
font-size: $u-action-sheet-item-wrap-name-font-size;
|
||||
color: $u-main-color;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&__subname {
|
||||
font-size: $u-action-sheet-item-wrap-subname-font-size;
|
||||
color: $u-action-sheet-item-wrap-subname-color;
|
||||
margin-top: $u-action-sheet-item-wrap-subname-margin-top;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__cancel-text {
|
||||
font-size: $u-action-sheet-cancel-text-font-size;
|
||||
color: $u-action-sheet-cancel-text-color;
|
||||
text-align: center;
|
||||
padding: $u-action-sheet-cancel-text-font-size;
|
||||
}
|
||||
|
||||
&--hover {
|
||||
background-color: $u-action-sheet-cancel-text-hover-background-color;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,59 +0,0 @@
|
|||
export default {
|
||||
props: {
|
||||
// 图片地址,Array<String>|Array<Object>形式
|
||||
urls: {
|
||||
type: Array,
|
||||
default: uni.$u.props.album.urls
|
||||
},
|
||||
// 指定从数组的对象元素中读取哪个属性作为图片地址
|
||||
keyName: {
|
||||
type: String,
|
||||
default: uni.$u.props.album.keyName
|
||||
},
|
||||
// 单图时,图片长边的长度
|
||||
singleSize: {
|
||||
type: [String, Number],
|
||||
default: uni.$u.props.album.singleSize
|
||||
},
|
||||
// 多图时,图片边长
|
||||
multipleSize: {
|
||||
type: [String, Number],
|
||||
default: uni.$u.props.album.multipleSize
|
||||
},
|
||||
// 多图时,图片水平和垂直之间的间隔
|
||||
space: {
|
||||
type: [String, Number],
|
||||
default: uni.$u.props.album.space
|
||||
},
|
||||
// 单图时,图片缩放裁剪的模式
|
||||
singleMode: {
|
||||
type: String,
|
||||
default: uni.$u.props.album.singleMode
|
||||
},
|
||||
// 多图时,图片缩放裁剪的模式
|
||||
multipleMode: {
|
||||
type: String,
|
||||
default: uni.$u.props.album.multipleMode
|
||||
},
|
||||
// 最多展示的图片数量,超出时最后一个位置将会显示剩余图片数量
|
||||
maxCount: {
|
||||
type: [String, Number],
|
||||
default: uni.$u.props.album.maxCount
|
||||
},
|
||||
// 是否可以预览图片
|
||||
previewFullImage: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.album.previewFullImage
|
||||
},
|
||||
// 每行展示图片数量,如设置,singleSize和multipleSize将会无效
|
||||
rowCount: {
|
||||
type: [String, Number],
|
||||
default: uni.$u.props.album.rowCount
|
||||
},
|
||||
// 超出maxCount时是否显示查看更多的提示
|
||||
showMore: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.album.showMore
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,259 +0,0 @@
|
|||
<template>
|
||||
<view class="u-album">
|
||||
<view
|
||||
class="u-album__row"
|
||||
ref="u-album__row"
|
||||
v-for="(arr, index) in showUrls"
|
||||
:forComputedUse="albumWidth"
|
||||
:key="index"
|
||||
>
|
||||
<view
|
||||
class="u-album__row__wrapper"
|
||||
v-for="(item, index1) in arr"
|
||||
:key="index1"
|
||||
:style="[imageStyle(index + 1, index1 + 1)]"
|
||||
@tap="previewFullImage ? onPreviewTap(getSrc(item)) : ''"
|
||||
>
|
||||
<image
|
||||
:src="getSrc(item)"
|
||||
:mode="
|
||||
urls.length === 1
|
||||
? imageHeight > 0
|
||||
? singleMode
|
||||
: 'widthFix'
|
||||
: multipleMode
|
||||
"
|
||||
:style="[
|
||||
{
|
||||
width: imageWidth,
|
||||
height: imageHeight
|
||||
}
|
||||
]"
|
||||
></image>
|
||||
<view
|
||||
v-if="
|
||||
showMore &&
|
||||
urls.length > rowCount * showUrls.length &&
|
||||
index === showUrls.length - 1 &&
|
||||
index1 === showUrls[showUrls.length - 1].length - 1
|
||||
"
|
||||
class="u-album__row__wrapper__text"
|
||||
>
|
||||
<u--text
|
||||
:text="`+${urls.length - maxCount}`"
|
||||
color="#fff"
|
||||
:size="multipleSize * 0.3"
|
||||
align="center"
|
||||
customStyle="justify-content: center"
|
||||
></u--text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import props from './props.js'
|
||||
// #ifdef APP-NVUE
|
||||
// 由于weex为阿里的KPI业绩考核的产物,所以不支持百分比单位,这里需要通过dom查询组件的宽度
|
||||
const dom = uni.requireNativePlugin('dom')
|
||||
// #endif
|
||||
|
||||
/**
|
||||
* Album 相册
|
||||
* @description 本组件提供一个类似相册的功能,让开发者开发起来更加得心应手。减少重复的模板代码
|
||||
* @tutorial https://www.uviewui.com/components/album.html
|
||||
*
|
||||
* @property {Array} urls 图片地址列表 Array<String>|Array<Object>形式
|
||||
* @property {String} keyName 指定从数组的对象元素中读取哪个属性作为图片地址
|
||||
* @property {String | Number} singleSize 单图时,图片长边的长度 (默认 180 )
|
||||
* @property {String | Number} multipleSize 多图时,图片边长 (默认 70 )
|
||||
* @property {String | Number} space 多图时,图片水平和垂直之间的间隔 (默认 6 )
|
||||
* @property {String} singleMode 单图时,图片缩放裁剪的模式 (默认 'scaleToFill' )
|
||||
* @property {String} multipleMode 多图时,图片缩放裁剪的模式 (默认 'aspectFill' )
|
||||
* @property {String | Number} maxCount 取消按钮的提示文字 (默认 9 )
|
||||
* @property {Boolean} previewFullImage 是否可以预览图片 (默认 true )
|
||||
* @property {String | Number} rowCount 每行展示图片数量,如设置,singleSize和multipleSize将会无效 (默认 3 )
|
||||
* @property {Boolean} showMore 超出maxCount时是否显示查看更多的提示 (默认 true )
|
||||
*
|
||||
* @event {Function} albumWidth 某些特殊的情况下,需要让文字与相册的宽度相等,这里事件的形式对外发送 (回调参数 width )
|
||||
* @example <u-album :urls="urls2" @albumWidth="width => albumWidth = width" multipleSize="68" ></u-album>
|
||||
*/
|
||||
export default {
|
||||
name: 'u-album',
|
||||
mixins: [uni.$u.mpMixin, uni.$u.mixin, props],
|
||||
data() {
|
||||
return {
|
||||
// 单图的宽度
|
||||
singleWidth: 0,
|
||||
// 单图的高度
|
||||
singleHeight: 0,
|
||||
// 单图时,如果无法获取图片的尺寸信息,让图片宽度默认为容器的一定百分比
|
||||
singlePercent: 0.6
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
urls: {
|
||||
immediate: true,
|
||||
handler(newVal) {
|
||||
if (newVal.length === 1) {
|
||||
this.getImageRect()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
imageStyle() {
|
||||
return (index1, index2) => {
|
||||
const { space, rowCount, multipleSize, urls } = this,
|
||||
{ addUnit, addStyle } = uni.$u,
|
||||
rowLen = this.showUrls.length,
|
||||
allLen = this.urls.length
|
||||
const style = {
|
||||
marginRight: addUnit(space),
|
||||
marginBottom: addUnit(space)
|
||||
}
|
||||
// 如果为最后一行,则每个图片都无需下边框
|
||||
if (index1 === rowLen) style.marginBottom = 0
|
||||
// 每行的最右边一张和总长度的最后一张无需右边框
|
||||
if (
|
||||
index2 === rowCount ||
|
||||
(index1 === rowLen &&
|
||||
index2 === this.showUrls[index1 - 1].length)
|
||||
)
|
||||
style.marginRight = 0
|
||||
return style
|
||||
}
|
||||
},
|
||||
// 将数组划分为二维数组
|
||||
showUrls() {
|
||||
const arr = []
|
||||
this.urls.map((item, index) => {
|
||||
// 限制最大展示数量
|
||||
if (index + 1 <= this.maxCount) {
|
||||
// 计算该元素为第几个素组内
|
||||
const itemIndex = Math.floor(index / this.rowCount)
|
||||
// 判断对应的索引是否存在
|
||||
if (!arr[itemIndex]) {
|
||||
arr[itemIndex] = []
|
||||
}
|
||||
arr[itemIndex].push(item)
|
||||
}
|
||||
})
|
||||
return arr
|
||||
},
|
||||
imageWidth() {
|
||||
return uni.$u.addUnit(
|
||||
this.urls.length === 1 ? this.singleWidth : this.multipleSize
|
||||
)
|
||||
},
|
||||
imageHeight() {
|
||||
return uni.$u.addUnit(
|
||||
this.urls.length === 1 ? this.singleHeight : this.multipleSize
|
||||
)
|
||||
},
|
||||
// 此变量无实际用途,仅仅是为了利用computed特性,让其在urls长度等变化时,重新计算图片的宽度
|
||||
// 因为用户在某些特殊的情况下,需要让文字与相册的宽度相等,所以这里事件的形式对外发送
|
||||
albumWidth() {
|
||||
let width = 0
|
||||
if (this.urls.length === 1) {
|
||||
width = this.singleWidth
|
||||
} else {
|
||||
width =
|
||||
this.showUrls[0].length * this.multipleSize +
|
||||
this.space * (this.showUrls[0].length - 1)
|
||||
}
|
||||
this.$emit('albumWidth', width)
|
||||
return width
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 预览图片
|
||||
onPreviewTap(url) {
|
||||
const urls = this.urls.map((item) => {
|
||||
return this.getSrc(item)
|
||||
})
|
||||
uni.previewImage({
|
||||
current: url,
|
||||
urls
|
||||
})
|
||||
},
|
||||
// 获取图片的路径
|
||||
getSrc(item) {
|
||||
return uni.$u.test.object(item)
|
||||
? (this.keyName && item[this.keyName]) || item.src
|
||||
: item
|
||||
},
|
||||
// 单图时,获取图片的尺寸
|
||||
// 在小程序中,需要将网络图片的的域名添加到小程序的download域名才可能获取尺寸
|
||||
// 在没有添加的情况下,让单图宽度默认为盒子的一定宽度(singlePercent)
|
||||
getImageRect() {
|
||||
const src = this.getSrc(this.urls[0])
|
||||
uni.getImageInfo({
|
||||
src,
|
||||
success: (res) => {
|
||||
// 判断图片横向还是竖向展示方式
|
||||
const isHorizotal = res.width >= res.height
|
||||
this.singleWidth = isHorizotal
|
||||
? this.singleSize
|
||||
: (res.width / res.height) * this.singleSize
|
||||
this.singleHeight = !isHorizotal
|
||||
? this.singleSize
|
||||
: (res.height / res.width) * this.singleWidth
|
||||
},
|
||||
fail: () => {
|
||||
this.getComponentWidth()
|
||||
}
|
||||
})
|
||||
},
|
||||
// 获取组件的宽度
|
||||
async getComponentWidth() {
|
||||
// 延时一定时间,以获取dom尺寸
|
||||
await uni.$u.sleep(30)
|
||||
// #ifndef APP-NVUE
|
||||
this.$uGetRect('.u-album__row').then((size) => {
|
||||
this.singleWidth = size.width * this.singlePercent
|
||||
})
|
||||
// #endif
|
||||
|
||||
// #ifdef APP-NVUE
|
||||
// 这里ref="u-album__row"所在的标签为通过for循环出来,导致this.$refs['u-album__row']是一个数组
|
||||
const ref = this.$refs['u-album__row'][0]
|
||||
ref &&
|
||||
dom.getComponentRect(ref, (res) => {
|
||||
this.singleWidth = res.size.width * this.singlePercent
|
||||
})
|
||||
// #endif
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../libs/css/components.scss';
|
||||
|
||||
.u-album {
|
||||
@include flex(column);
|
||||
|
||||
&__row {
|
||||
@include flex(row);
|
||||
flex-wrap: wrap;
|
||||
|
||||
&__wrapper {
|
||||
position: relative;
|
||||
|
||||
&__text {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
@include flex(row);
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,44 +0,0 @@
|
|||
export default {
|
||||
props: {
|
||||
// 显示文字
|
||||
title: {
|
||||
type: String,
|
||||
default: uni.$u.props.alert.title
|
||||
},
|
||||
// 主题,success/warning/info/error
|
||||
type: {
|
||||
type: String,
|
||||
default: uni.$u.props.alert.type
|
||||
},
|
||||
// 辅助性文字
|
||||
description: {
|
||||
type: String,
|
||||
default: uni.$u.props.alert.description
|
||||
},
|
||||
// 是否可关闭
|
||||
closable: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.alert.closable
|
||||
},
|
||||
// 是否显示图标
|
||||
showIcon: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.alert.showIcon
|
||||
},
|
||||
// 浅或深色调,light-浅色,dark-深色
|
||||
effect: {
|
||||
type: String,
|
||||
default: uni.$u.props.alert.effect
|
||||
},
|
||||
// 文字是否居中
|
||||
center: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.alert.center
|
||||
},
|
||||
// 字体大小
|
||||
fontSize: {
|
||||
type: [String, Number],
|
||||
default: uni.$u.props.alert.fontSize
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,243 +0,0 @@
|
|||
<template>
|
||||
<u-transition
|
||||
mode="fade"
|
||||
:show="show"
|
||||
>
|
||||
<view
|
||||
class="u-alert"
|
||||
:class="[`u-alert--${type}--${effect}`]"
|
||||
@tap.stop="clickHandler"
|
||||
:style="[$u.addStyle(customStyle)]"
|
||||
>
|
||||
<view
|
||||
class="u-alert__icon"
|
||||
v-if="showIcon"
|
||||
>
|
||||
<u-icon
|
||||
:name="iconName"
|
||||
size="18"
|
||||
:color="iconColor"
|
||||
></u-icon>
|
||||
</view>
|
||||
<view
|
||||
class="u-alert__content"
|
||||
:style="[{
|
||||
paddingRight: closable ? '20px' : 0
|
||||
}]"
|
||||
>
|
||||
<text
|
||||
class="u-alert__content__title"
|
||||
v-if="title"
|
||||
:style="[{
|
||||
fontSize: $u.addUnit(fontSize),
|
||||
textAlign: center ? 'center' : 'left'
|
||||
}]"
|
||||
:class="[effect === 'dark' ? 'u-alert__text--dark' : `u-alert__text--${type}--light`]"
|
||||
>{{ title }}</text>
|
||||
<text
|
||||
class="u-alert__content__desc"
|
||||
v-if="description"
|
||||
:style="[{
|
||||
fontSize: $u.addUnit(fontSize),
|
||||
textAlign: center ? 'center' : 'left'
|
||||
}]"
|
||||
:class="[effect === 'dark' ? 'u-alert__text--dark' : `u-alert__text--${type}--light`]"
|
||||
>{{ description }}</text>
|
||||
</view>
|
||||
<view
|
||||
class="u-alert__close"
|
||||
v-if="closable"
|
||||
@tap.stop="closeHandler"
|
||||
>
|
||||
<u-icon
|
||||
name="close"
|
||||
:color="iconColor"
|
||||
size="15"
|
||||
></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
</u-transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import props from './props.js';
|
||||
/**
|
||||
* Alert 警告提示
|
||||
* @description 警告提示,展现需要关注的信息。
|
||||
* @tutorial https://www.uviewui.com/components/alertTips.html
|
||||
*
|
||||
* @property {String} title 显示的文字
|
||||
* @property {String} type 使用预设的颜色 (默认 'warning' )
|
||||
* @property {String} description 辅助性文字,颜色比title浅一点,字号也小一点,可选
|
||||
* @property {Boolean} closable 关闭按钮(默认为叉号icon图标) (默认 false )
|
||||
* @property {Boolean} showIcon 是否显示左边的辅助图标 ( 默认 false )
|
||||
* @property {String} effect 多图时,图片缩放裁剪的模式 (默认 'light' )
|
||||
* @property {Boolean} center 文字是否居中 (默认 false )
|
||||
* @property {String | Number} fontSize 字体大小 (默认 14 )
|
||||
* @property {Object} customStyle 定义需要用到的外部样式
|
||||
* @event {Function} click 点击组件时触发
|
||||
* @example <u-alert :title="title" type = "warning" :closable="closable" :description = "description"></u-alert>
|
||||
*/
|
||||
export default {
|
||||
name: 'u-alert',
|
||||
mixins: [uni.$u.mpMixin, uni.$u.mixin, props],
|
||||
data() {
|
||||
return {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
iconColor() {
|
||||
return this.effect === 'light' ? this.type : '#fff'
|
||||
},
|
||||
// 不同主题对应不同的图标
|
||||
iconName() {
|
||||
switch (this.type) {
|
||||
case 'success':
|
||||
return 'checkmark-circle-fill';
|
||||
break;
|
||||
case 'error':
|
||||
return 'close-circle-fill';
|
||||
break;
|
||||
case 'warning':
|
||||
return 'error-circle-fill';
|
||||
break;
|
||||
case 'info':
|
||||
return 'info-circle-fill';
|
||||
break;
|
||||
case 'primary':
|
||||
return 'more-circle-fill';
|
||||
break;
|
||||
default:
|
||||
return 'error-circle-fill';
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 点击内容
|
||||
clickHandler() {
|
||||
this.$emit('click')
|
||||
},
|
||||
// 点击关闭按钮
|
||||
closeHandler() {
|
||||
this.show = false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/components.scss";
|
||||
|
||||
.u-alert {
|
||||
position: relative;
|
||||
background-color: $u-primary;
|
||||
padding: 8px 10px;
|
||||
@include flex(row);
|
||||
align-items: center;
|
||||
border-top-left-radius: 4px;
|
||||
border-top-right-radius: 4px;
|
||||
border-bottom-left-radius: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
|
||||
&--primary--dark {
|
||||
background-color: $u-primary;
|
||||
}
|
||||
|
||||
&--primary--light {
|
||||
background-color: #ecf5ff;
|
||||
}
|
||||
|
||||
&--error--dark {
|
||||
background-color: $u-error;
|
||||
}
|
||||
|
||||
&--error--light {
|
||||
background-color: #FEF0F0;
|
||||
}
|
||||
|
||||
&--success--dark {
|
||||
background-color: $u-success;
|
||||
}
|
||||
|
||||
&--success--light {
|
||||
background-color: #f5fff0;
|
||||
}
|
||||
|
||||
&--warning--dark {
|
||||
background-color: $u-warning;
|
||||
}
|
||||
|
||||
&--warning--light {
|
||||
background-color: #FDF6EC;
|
||||
}
|
||||
|
||||
&--info--dark {
|
||||
background-color: $u-info;
|
||||
}
|
||||
|
||||
&--info--light {
|
||||
background-color: #f4f4f5;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
&__content {
|
||||
@include flex(column);
|
||||
flex: 1;
|
||||
|
||||
&__title {
|
||||
color: $u-main-color;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
&__desc {
|
||||
color: $u-main-color;
|
||||
font-size: 14px;
|
||||
flex-wrap: wrap;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
&__title--dark,
|
||||
&__desc--dark {
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
&__text--primary--light,
|
||||
&__text--primary--light {
|
||||
color: $u-primary;
|
||||
}
|
||||
|
||||
&__text--success--light,
|
||||
&__text--success--light {
|
||||
color: $u-success;
|
||||
}
|
||||
|
||||
&__text--warning--light,
|
||||
&__text--warning--light {
|
||||
color: $u-warning;
|
||||
}
|
||||
|
||||
&__text--error--light,
|
||||
&__text--error--light {
|
||||
color: $u-error;
|
||||
}
|
||||
|
||||
&__text--info--light,
|
||||
&__text--info--light {
|
||||
color: $u-info;
|
||||
}
|
||||
|
||||
&__close {
|
||||
position: absolute;
|
||||
top: 11px;
|
||||
right: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,52 +0,0 @@
|
|||
export default {
|
||||
props: {
|
||||
// 头像图片组
|
||||
urls: {
|
||||
type: Array,
|
||||
default: uni.$u.props.avatarGroup.urls
|
||||
},
|
||||
// 最多展示的头像数量
|
||||
maxCount: {
|
||||
type: [String, Number],
|
||||
default: uni.$u.props.avatarGroup.maxCount
|
||||
},
|
||||
// 头像形状
|
||||
shape: {
|
||||
type: String,
|
||||
default: uni.$u.props.avatarGroup.shape
|
||||
},
|
||||
// 图片裁剪模式
|
||||
mode: {
|
||||
type: String,
|
||||
default: uni.$u.props.avatarGroup.mode
|
||||
},
|
||||
// 超出maxCount时是否显示查看更多的提示
|
||||
showMore: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.avatarGroup.showMore
|
||||
},
|
||||
// 头像大小
|
||||
size: {
|
||||
type: [String, Number],
|
||||
default: uni.$u.props.avatarGroup.size
|
||||
},
|
||||
// 指定从数组的对象元素中读取哪个属性作为图片地址
|
||||
keyName: {
|
||||
type: String,
|
||||
default: uni.$u.props.avatarGroup.keyName
|
||||
},
|
||||
// 头像之间的遮挡比例
|
||||
gap: {
|
||||
type: [String, Number],
|
||||
validator(value) {
|
||||
return value >= 0 && value <= 1
|
||||
},
|
||||
default: uni.$u.props.avatarGroup.gap
|
||||
},
|
||||
// 需额外显示的值
|
||||
extraValue: {
|
||||
type: [Number, String],
|
||||
default: uni.$u.props.avatarGroup.extraValue
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,103 +0,0 @@
|
|||
<template>
|
||||
<view class="u-avatar-group">
|
||||
<view
|
||||
class="u-avatar-group__item"
|
||||
v-for="(item, index) in showUrl"
|
||||
:key="index"
|
||||
:style="{
|
||||
marginLeft: index === 0 ? 0 : $u.addUnit(-size * gap)
|
||||
}"
|
||||
>
|
||||
<u-avatar
|
||||
:size="size"
|
||||
:shape="shape"
|
||||
:mode="mode"
|
||||
:src="$u.test.object(item) ? keyName && item[keyName] || item.url : item"
|
||||
></u-avatar>
|
||||
<view
|
||||
class="u-avatar-group__item__show-more"
|
||||
v-if="showMore && index === showUrl.length - 1 && (urls.length > maxCount || extraValue > 0)"
|
||||
@tap="clickHandler"
|
||||
>
|
||||
<u--text
|
||||
color="#ffffff"
|
||||
:size="size * 0.4"
|
||||
:text="`+${extraValue || urls.length - showUrl.length}`"
|
||||
align="center"
|
||||
customStyle="justify-content: center"
|
||||
></u--text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import props from './props.js';
|
||||
/**
|
||||
* AvatarGroup 头像组
|
||||
* @description 本组件一般用于展示头像的地方,如个人中心,或者评论列表页的用户头像展示等场所。
|
||||
* @tutorial https://www.uviewui.com/components/avatar.html
|
||||
*
|
||||
* @property {Array} urls 头像图片组 (默认 [] )
|
||||
* @property {String | Number} maxCount 最多展示的头像数量 ( 默认 5 )
|
||||
* @property {String} shape 头像形状( 'circle' (默认) | 'square' )
|
||||
* @property {String} mode 图片裁剪模式(默认 'scaleToFill' )
|
||||
* @property {Boolean} showMore 超出maxCount时是否显示查看更多的提示 (默认 true )
|
||||
* @property {String | Number} size 头像大小 (默认 40 )
|
||||
* @property {String} keyName 指定从数组的对象元素中读取哪个属性作为图片地址
|
||||
* @property {String | Number} gap 头像之间的遮挡比例(0.4代表遮挡40%) (默认 0.5 )
|
||||
* @property {String | Number} extraValue 需额外显示的值
|
||||
* @event {Function} showMore 头像组更多点击
|
||||
* @example <u-avatar-group:urls="urls" size="35" gap="0.4" ></u-avatar-group:urls=>
|
||||
*/
|
||||
export default {
|
||||
name: 'u-avatar-group',
|
||||
mixins: [uni.$u.mpMixin, uni.$u.mixin, props],
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
showUrl() {
|
||||
return this.urls.slice(0, this.maxCount)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
clickHandler() {
|
||||
this.$emit('showMore')
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/components.scss";
|
||||
|
||||
.u-avatar-group {
|
||||
@include flex;
|
||||
|
||||
&__item {
|
||||
margin-left: -10px;
|
||||
position: relative;
|
||||
|
||||
&--no-indent {
|
||||
// 如果你想质疑作者不会使用:first-child,说明你太年轻,因为nvue不支持
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
&__show-more {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
@include flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 100px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,78 +0,0 @@
|
|||
export default {
|
||||
props: {
|
||||
// 头像图片路径(不能为相对路径)
|
||||
src: {
|
||||
type: String,
|
||||
default: uni.$u.props.avatar.src
|
||||
},
|
||||
// 头像形状,circle-圆形,square-方形
|
||||
shape: {
|
||||
type: String,
|
||||
default: uni.$u.props.avatar.shape
|
||||
},
|
||||
// 头像尺寸
|
||||
size: {
|
||||
type: [String, Number],
|
||||
default: uni.$u.props.avatar.size
|
||||
},
|
||||
// 裁剪模式
|
||||
mode: {
|
||||
type: String,
|
||||
default: uni.$u.props.avatar.mode
|
||||
},
|
||||
// 显示的文字
|
||||
text: {
|
||||
type: String,
|
||||
default: uni.$u.props.avatar.text
|
||||
},
|
||||
// 背景色
|
||||
bgColor: {
|
||||
type: String,
|
||||
default: uni.$u.props.avatar.bgColor
|
||||
},
|
||||
// 文字颜色
|
||||
color: {
|
||||
type: String,
|
||||
default: uni.$u.props.avatar.color
|
||||
},
|
||||
// 文字大小
|
||||
fontSize: {
|
||||
type: [String, Number],
|
||||
default: uni.$u.props.avatar.fontSize
|
||||
},
|
||||
// 显示的图标
|
||||
icon: {
|
||||
type: String,
|
||||
default: uni.$u.props.avatar.icon
|
||||
},
|
||||
// 显示小程序头像,只对百度,微信,QQ小程序有效
|
||||
mpAvatar: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.avatar.mpAvatar
|
||||
},
|
||||
// 是否使用随机背景色
|
||||
randomBgColor: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.avatar.randomBgColor
|
||||
},
|
||||
// 加载失败的默认头像(组件有内置默认图片)
|
||||
defaultUrl: {
|
||||
type: String,
|
||||
default: uni.$u.props.avatar.defaultUrl
|
||||
},
|
||||
// 如果配置了randomBgColor为true,且配置了此值,则从默认的背景色数组中取出对应索引的颜色值,取值0-19之间
|
||||
colorIndex: {
|
||||
type: [String, Number],
|
||||
// 校验参数规则,索引在0-19之间
|
||||
validator(n) {
|
||||
return uni.$u.test.range(n, [0, 19]) || n === ''
|
||||
},
|
||||
default: uni.$u.props.avatar.colorIndex
|
||||
},
|
||||
// 组件标识符
|
||||
name: {
|
||||
type: String,
|
||||
default: uni.$u.props.avatar.name
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,172 +0,0 @@
|
|||
<template>
|
||||
<view
|
||||
class="u-avatar"
|
||||
:class="[`u-avatar--${shape}`]"
|
||||
:style="[{
|
||||
backgroundColor: (text || icon) ? (randomBgColor ? colors[colorIndex !== '' ? colorIndex : $u.random(0, 19)] : bgColor) : 'transparent',
|
||||
width: $u.addUnit(size),
|
||||
height: $u.addUnit(size),
|
||||
}, $u.addStyle(customStyle)]"
|
||||
@tap="clickHandler"
|
||||
>
|
||||
<slot>
|
||||
<!-- #ifdef MP-WEIXIN || MP-QQ || MP-BAIDU -->
|
||||
<open-data
|
||||
v-if="mpAvatar && allowMp"
|
||||
type="userAvatarUrl"
|
||||
:style="[{
|
||||
width: $u.addUnit(size),
|
||||
height: $u.addUnit(size)
|
||||
}]"
|
||||
/>
|
||||
<!-- #endif -->
|
||||
<!-- #ifndef MP-WEIXIN && MP-QQ && MP-BAIDU -->
|
||||
<template v-if="mpAvatar && allowMp"></template>
|
||||
<!-- #endif -->
|
||||
<u-icon
|
||||
v-else-if="icon"
|
||||
:name="icon"
|
||||
:size="fontSize"
|
||||
:color="color"
|
||||
></u-icon>
|
||||
<u--text
|
||||
v-else-if="text"
|
||||
:text="text"
|
||||
:size="fontSize"
|
||||
:color="color"
|
||||
align="center"
|
||||
customStyle="justify-content: center"
|
||||
></u--text>
|
||||
<image
|
||||
class="u-avatar__image"
|
||||
v-else
|
||||
:class="[`u-avatar__image--${shape}`]"
|
||||
:src="avatarUrl || defaultUrl"
|
||||
:mode="mode"
|
||||
@error="errorHandler"
|
||||
:style="[{
|
||||
width: $u.addUnit(size),
|
||||
height: $u.addUnit(size)
|
||||
}]"
|
||||
></image>
|
||||
</slot>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import props from './props.js';
|
||||
const base64Avatar =
|
||||
"data:image/jpg;base64,/9j/4QAYRXhpZgAASUkqAAgAAAAAAAAAAAAAAP/sABFEdWNreQABAAQAAAA8AAD/4QMraHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLwA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/PiA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjMtYzAxMSA2Ni4xNDU2NjEsIDIwMTIvMDIvMDYtMTQ6NTY6MjcgICAgICAgICI+IDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+IDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bXA6Q3JlYXRvclRvb2w9IkFkb2JlIFBob3Rvc2hvcCBDUzYgKFdpbmRvd3MpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjREMEQwRkY0RjgwNDExRUE5OTY2RDgxODY3NkJFODMxIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjREMEQwRkY1RjgwNDExRUE5OTY2RDgxODY3NkJFODMxIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6NEQwRDBGRjJGODA0MTFFQTk5NjZEODE4Njc2QkU4MzEiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6NEQwRDBGRjNGODA0MTFFQTk5NjZEODE4Njc2QkU4MzEiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz7/7gAOQWRvYmUAZMAAAAAB/9sAhAAGBAQEBQQGBQUGCQYFBgkLCAYGCAsMCgoLCgoMEAwMDAwMDBAMDg8QDw4MExMUFBMTHBsbGxwfHx8fHx8fHx8fAQcHBw0MDRgQEBgaFREVGh8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx//wAARCADIAMgDAREAAhEBAxEB/8QAcQABAQEAAwEBAAAAAAAAAAAAAAUEAQMGAgcBAQAAAAAAAAAAAAAAAAAAAAAQAAIBAwICBgkDBQAAAAAAAAABAhEDBCEFMVFBYXGREiKBscHRMkJSEyOh4XLxYjNDFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEQMRAD8A/fAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHbHFyZ/Dam+yLA+Z2L0Pjtyj2poD4AAAAAAAAAAAAAAAAAAAAAAAAKWFs9y6lcvvwQeqj8z9wFaziY1n/HbUX9XF97A7QAGXI23EvJ1goyfzR0YEfN269jeZ+a03pNe0DIAAAAAAAAAAAAAAAAAAAACvtO3RcVkXlWutuL9YFYAAAAAOJRjKLjJVi9GmB5/csH/mu1h/in8PU+QGMAAAAAAAAAAAAAAAAAAaMDG/6MmMH8C80+xAelSSVFolwQAAAAAAAHVlWI37ErUulaPk+hgeYnCUJuElSUXRrrQHAAAAAAAAAAAAAAAAABa2Oz4bM7r4zdF2ICmAAAAAAAAAg7zZ8GX41wuJP0rRgYAAAAAAAAAAAAAAAAAD0m2R8ODaXU33tsDSAAAAAAAAAlb9HyWZcnJd9PcBHAAAAAAAAAAAAAAAAAPS7e64Vn+KA0AAAAAAAAAJm+v8Ftf3ewCKAAAAAAAAAAAAAAAAAX9muqeGo9NttP06+0DcAAAAAAAAAjb7dTu2ra+VOT9P8AQCWAAAAAAAAAAAAAAAAAUNmyPt5Ltv4bui/kuAF0AAAAAAADiUlGLlJ0SVW+oDzOXfd/Ind6JPRdS0QHSAAAAAAAAAAAAAAAAAE2nVaNcGB6Lbs6OTao9LsF51z60BrAAAAAABJ3jOVHjW3r/sa9QEgAAAAAAAAAAAAAAAAAAAPu1duWriuW34ZR4MC9hbnZyEoy8l36XwfYBsAAADaSq9EuLAlZ+7xSdrGdW9Hc5dgEdtt1erfFgAAAAAAAAAAAAAAAAADVjbblX6NR8MH80tEBRs7HYivyzlN8lovaBPzduvY0m6eK10TXtAyAarO55lpJK54orolr+4GqO/Xaea1FvqbXvA+Z77kNeW3GPbV+4DJfzcm/pcm3H6Vou5AdAFLC2ed2Pjv1txa8sV8T6wOL+yZEKu1JXFy4MDBOE4ScZxcZLinoB8gAAAAAAAAAAAB242LeyJ+C3GvN9C7QLmJtePYpKS+5c+p8F2IDYAANJqj1T4oCfk7Nj3G5Wn9qXJax7gJ93Z82D8sVNc4v30A6Xg5i42Z+iLfqARwcyT0sz9MWvWBps7LlTf5Grce9/oBTxdtxseklHxT+uWr9AGoAB138ezfj4bsFJdD6V2MCPm7RdtJzs1uW1xXzL3gTgAAAAAAAAADRhYc8q74I6RWs5ckB6GxYtWLat21SK731sDsAAAAAAAAAAAAAAAASt021NO/YjrxuQXT1oCOAAAAAAABzGLlJRSq26JAelwsWONYjbXxcZvmwO8AAAAAAAAAAAAAAAAAAef3TEWPkVivx3NY9T6UBiAAAAAABo2+VmGXblddIJ8eivRUD0oAAAAAAAAAAAAAAAAAAAYt4tKeFKVNYNSXfRgefAAAAAAAAr7VuSSWPedKaW5v1MCsAAAAAAAAAAAAAAAAAAIe6bj96Ts2n+JPzSXzP3ATgAAAAAAAAFbbt1UUrOQ9FpC4/UwK6aaqtU+DAAAAAAAAAAAAAAA4lKMIuUmoxWrb4ARNx3R3q2rLpa4Sl0y/YCcAAAAAAAAAAANmFud7G8r89r6X0dgFvGzLGRGtuWvTF6NAdwAAAAAAAAAAAy5W442PVN+K59EePp5ARMvOv5MvO6QXCC4AZwAAAAAAAAAAAAAcxlKLUotprg1owN+PvORborq+7Hnwl3gUbO74VzRydt8pKn68ANcJwmqwkpLmnUDkAAAAfNy9atqtyagut0AxXt5xIV8Fbj6lRd7Am5G65V6qUvtwfyx94GMAAAAAAAAAAAAAAAAAAAOU2nVOj5gdsc3LiqRvTpyqwOxbnnrhdfpSfrQB7pnv/AGvuS9gHXPMy5/Fem1yq0v0A6W29XqwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf//Z";
|
||||
/**
|
||||
* Avatar 头像
|
||||
* @description 本组件一般用于展示头像的地方,如个人中心,或者评论列表页的用户头像展示等场所。
|
||||
* @tutorial https://www.uviewui.com/components/avatar.html
|
||||
*
|
||||
* @property {String} src 头像路径,如加载失败,将会显示默认头像(不能为相对路径)
|
||||
* @property {String} shape 头像形状 ( circle (默认) | square)
|
||||
* @property {String | Number} size 头像尺寸,可以为指定字符串(large, default, mini),或者数值 (默认 40 )
|
||||
* @property {String} mode 头像图片的裁剪类型,与uni的image组件的mode参数一致,如效果达不到需求,可尝试传widthFix值 (默认 'scaleToFill' )
|
||||
* @property {String} text 用文字替代图片,级别优先于src
|
||||
* @property {String} bgColor 背景颜色,一般显示文字时用 (默认 '#c0c4cc' )
|
||||
* @property {String} color 文字颜色 (默认 '#ffffff' )
|
||||
* @property {String | Number} fontSize 文字大小 (默认 18 )
|
||||
* @property {String} icon 显示的图标
|
||||
* @property {Boolean} mpAvatar 显示小程序头像,只对百度,微信,QQ小程序有效 (默认 false )
|
||||
* @property {Boolean} randomBgColor 是否使用随机背景色 (默认 false )
|
||||
* @property {String} defaultUrl 加载失败的默认头像(组件有内置默认图片)
|
||||
* @property {String | Number} colorIndex 如果配置了randomBgColor为true,且配置了此值,则从默认的背景色数组中取出对应索引的颜色值,取值0-19之间
|
||||
* @property {String} name 组件标识符 (默认 'level' )
|
||||
* @property {Object} customStyle 定义需要用到的外部样式
|
||||
*
|
||||
* @event {Function} click 点击组件时触发 index: 用户传递的标识符
|
||||
* @example <u-avatar :src="src" mode="square"></u-avatar>
|
||||
*/
|
||||
export default {
|
||||
name: 'u-avatar',
|
||||
mixins: [uni.$u.mpMixin, uni.$u.mixin, props],
|
||||
data() {
|
||||
return {
|
||||
// 如果配置randomBgColor参数为true,在图标或者文字的模式下,会随机从中取出一个颜色值当做背景色
|
||||
colors: ['#ffb34b', '#f2bba9', '#f7a196', '#f18080', '#88a867', '#bfbf39', '#89c152', '#94d554', '#f19ec2',
|
||||
'#afaae4', '#e1b0df', '#c38cc1', '#72dcdc', '#9acdcb', '#77b1cc', '#448aca', '#86cefa', '#98d1ee',
|
||||
'#73d1f1',
|
||||
'#80a7dc'
|
||||
],
|
||||
avatarUrl: this.src,
|
||||
allowMp: false
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// 监听头像src的变化,赋值给内部的avatarUrl变量,因为图片加载失败时,需要修改图片的src为默认值
|
||||
// 而组件内部不能直接修改props的值,所以需要一个中间变量
|
||||
src: {
|
||||
immediate: true,
|
||||
handler(newVal) {
|
||||
this.avatarUrl = newVal
|
||||
// 如果没有传src,则主动触发error事件,用于显示默认的头像,否则src为''空字符等的时候,会无内容展示
|
||||
if(!newVal) {
|
||||
this.errorHandler()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
imageStyle() {
|
||||
const style = {}
|
||||
return style
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.init()
|
||||
},
|
||||
methods: {
|
||||
init() {
|
||||
// 目前只有这几个小程序平台具有open-data标签
|
||||
// 其他平台可以通过uni.getUserInfo类似接口获取信息,但是需要弹窗授权(首次),不合符组件逻辑
|
||||
// 故目前自动获取小程序头像只支持这几个平台
|
||||
// #ifdef MP-WEIXIN || MP-QQ || MP-BAIDU
|
||||
this.allowMp = true
|
||||
// #endif
|
||||
},
|
||||
// 判断传入的name属性,是否图片路径,只要带有"/"均认为是图片形式
|
||||
isImg() {
|
||||
return this.src.indexOf('/') !== -1
|
||||
},
|
||||
// 图片加载时失败时触发
|
||||
errorHandler() {
|
||||
this.avatarUrl = this.defaultUrl || base64Avatar
|
||||
},
|
||||
clickHandler() {
|
||||
this.$emit('click', this.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/components.scss";
|
||||
|
||||
.u-avatar {
|
||||
@include flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&--circle {
|
||||
border-radius: 100px;
|
||||
}
|
||||
|
||||
&--square {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
&__image {
|
||||
&--circle {
|
||||
border-radius: 100px;
|
||||
}
|
||||
|
||||
&--square {
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,54 +0,0 @@
|
|||
export default {
|
||||
props: {
|
||||
// 返回顶部的形状,circle-圆形,square-方形
|
||||
mode: {
|
||||
type: String,
|
||||
default: uni.$u.props.backtop.mode
|
||||
},
|
||||
// 自定义图标
|
||||
icon: {
|
||||
type: String,
|
||||
default: uni.$u.props.backtop.icon
|
||||
},
|
||||
// 提示文字
|
||||
text: {
|
||||
type: String,
|
||||
default: uni.$u.props.backtop.text
|
||||
},
|
||||
// 返回顶部滚动时间
|
||||
duration: {
|
||||
type: [String, Number],
|
||||
default: uni.$u.props.backtop.duration
|
||||
},
|
||||
// 滚动距离
|
||||
scrollTop: {
|
||||
type: [String, Number],
|
||||
default: uni.$u.props.backtop.scrollTop
|
||||
},
|
||||
// 距离顶部多少距离显示,单位px
|
||||
top: {
|
||||
type: [String, Number],
|
||||
default: uni.$u.props.backtop.top
|
||||
},
|
||||
// 返回顶部按钮到底部的距离,单位px
|
||||
bottom: {
|
||||
type: [String, Number],
|
||||
default: uni.$u.props.backtop.bottom
|
||||
},
|
||||
// 返回顶部按钮到右边的距离,单位px
|
||||
right: {
|
||||
type: [String, Number],
|
||||
default: uni.$u.props.backtop.right
|
||||
},
|
||||
// 层级
|
||||
zIndex: {
|
||||
type: [String, Number],
|
||||
default: uni.$u.props.backtop.zIndex
|
||||
},
|
||||
// 图标的样式,对象形式
|
||||
iconStyle: {
|
||||
type: Object,
|
||||
default: uni.$u.props.backtop.iconStyle
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,129 +0,0 @@
|
|||
<template>
|
||||
<u-transition
|
||||
mode="fade"
|
||||
:customStyle="backTopStyle"
|
||||
:show="show"
|
||||
>
|
||||
<view
|
||||
class="u-back-top"
|
||||
:style="[contentStyle]"
|
||||
v-if="!$slots.default && !$slots.$default"
|
||||
@click="backToTop"
|
||||
>
|
||||
<u-icon
|
||||
:name="icon"
|
||||
:custom-style="iconStyle"
|
||||
></u-icon>
|
||||
<text
|
||||
v-if="text"
|
||||
class="u-back-top__text"
|
||||
>{{text}}</text>
|
||||
</view>
|
||||
<slot v-else />
|
||||
</u-transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import props from './props.js';
|
||||
// #ifdef APP-NVUE
|
||||
const dom = weex.requireModule('dom')
|
||||
// #endif
|
||||
/**
|
||||
* backTop 返回顶部
|
||||
* @description 本组件一个用于长页面,滑动一定距离后,出现返回顶部按钮,方便快速返回顶部的场景。
|
||||
* @tutorial https://uviewui.com/components/backTop.html
|
||||
*
|
||||
* @property {String} mode 返回顶部的形状,circle-圆形,square-方形 (默认 'circle' )
|
||||
* @property {String} icon 自定义图标 (默认 'arrow-upward' ) 见官方文档示例
|
||||
* @property {String} text 提示文字
|
||||
* @property {String | Number} duration 返回顶部滚动时间 (默认 100)
|
||||
* @property {String | Number} scrollTop 滚动距离 (默认 0 )
|
||||
* @property {String | Number} top 距离顶部多少距离显示,单位px (默认 400 )
|
||||
* @property {String | Number} bottom 返回顶部按钮到底部的距离,单位px (默认 100 )
|
||||
* @property {String | Number} right 返回顶部按钮到右边的距离,单位px (默认 20 )
|
||||
* @property {String | Number} zIndex 层级 (默认 9 )
|
||||
* @property {Object<Object>} iconStyle 图标的样式,对象形式 (默认 {color: '#909399',fontSize: '19px'})
|
||||
* @property {Object} customStyle 定义需要用到的外部样式
|
||||
*
|
||||
* @example <u-back-top :scrollTop="scrollTop"></u-back-top>
|
||||
*/
|
||||
export default {
|
||||
name: 'u-back-top',
|
||||
mixins: [uni.$u.mpMixin, uni.$u.mixin,props],
|
||||
computed: {
|
||||
backTopStyle() {
|
||||
// 动画组件样式
|
||||
const style = {
|
||||
bottom: uni.$u.addUnit(this.bottom),
|
||||
right: uni.$u.addUnit(this.right),
|
||||
width: '40px',
|
||||
height: '40px',
|
||||
position: 'fixed',
|
||||
zIndex: 10,
|
||||
}
|
||||
return style
|
||||
},
|
||||
show() {
|
||||
return uni.$u.getPx(this.scrollTop) > uni.$u.getPx(this.top)
|
||||
},
|
||||
contentStyle() {
|
||||
const style = {}
|
||||
let radius = 0
|
||||
// 是否圆形
|
||||
if(this.mode === 'circle') {
|
||||
radius = '100px'
|
||||
} else {
|
||||
radius = '4px'
|
||||
}
|
||||
// 为了兼容安卓nvue,只能这么分开写
|
||||
style.borderTopLeftRadius = radius
|
||||
style.borderTopRightRadius = radius
|
||||
style.borderBottomLeftRadius = radius
|
||||
style.borderBottomRightRadius = radius
|
||||
return uni.$u.deepMerge(style, uni.$u.addStyle(this.customStyle))
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
backToTop() {
|
||||
// #ifdef APP-NVUE
|
||||
if (!this.$parent.$refs['u-back-top']) {
|
||||
uni.$u.error(`nvue页面需要给页面最外层元素设置"ref='u-back-top'`)
|
||||
}
|
||||
dom.scrollToElement(this.$parent.$refs['u-back-top'], {
|
||||
offset: 0
|
||||
})
|
||||
// #endif
|
||||
|
||||
// #ifndef APP-NVUE
|
||||
uni.pageScrollTo({
|
||||
scrollTop: 0,
|
||||
duration: this.duration
|
||||
});
|
||||
// #endif
|
||||
this.$emit('click')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../libs/css/components.scss';
|
||||
$u-back-top-flex:1 !default;
|
||||
$u-back-top-height:100% !default;
|
||||
$u-back-top-background-color:#E1E1E1 !default;
|
||||
$u-back-top-tips-font-size:12px !default;
|
||||
.u-back-top {
|
||||
@include flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
flex:$u-back-top-flex;
|
||||
height: $u-back-top-height;
|
||||
justify-content: center;
|
||||
background-color: $u-back-top-background-color;
|
||||
|
||||
&__tips {
|
||||
font-size:$u-back-top-tips-font-size;
|
||||
transform: scale(0.8);
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,72 +0,0 @@
|
|||
export default {
|
||||
props: {
|
||||
// 是否显示圆点
|
||||
isDot: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.badge.isDot
|
||||
},
|
||||
// 显示的内容
|
||||
value: {
|
||||
type: [Number, String],
|
||||
default: uni.$u.props.badge.value
|
||||
},
|
||||
// 是否显示
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.badge.show
|
||||
},
|
||||
// 最大值,超过最大值会显示 '{max}+'
|
||||
max: {
|
||||
type: [Number, String],
|
||||
default: uni.$u.props.badge.max
|
||||
},
|
||||
// 主题类型,error|warning|success|primary
|
||||
type: {
|
||||
type: String,
|
||||
default: uni.$u.props.badge.type
|
||||
},
|
||||
// 当数值为 0 时,是否展示 Badge
|
||||
showZero: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.badge.showZero
|
||||
},
|
||||
// 背景颜色,优先级比type高,如设置,type参数会失效
|
||||
bgColor: {
|
||||
type: [String, null],
|
||||
default: uni.$u.props.badge.bgColor
|
||||
},
|
||||
// 字体颜色
|
||||
color: {
|
||||
type: [String, null],
|
||||
default: uni.$u.props.badge.color
|
||||
},
|
||||
// 徽标形状,circle-四角均为圆角,horn-左下角为直角
|
||||
shape: {
|
||||
type: String,
|
||||
default: uni.$u.props.badge.shape
|
||||
},
|
||||
// 设置数字的显示方式,overflow|ellipsis|limit
|
||||
// overflow会根据max字段判断,超出显示`${max}+`
|
||||
// ellipsis会根据max判断,超出显示`${max}...`
|
||||
// limit会依据1000作为判断条件,超出1000,显示`${value/1000}K`,比如2.2k、3.34w,最多保留2位小数
|
||||
numberType: {
|
||||
type: String,
|
||||
default: uni.$u.props.badge.numberType
|
||||
},
|
||||
// 设置badge的位置偏移,格式为 [x, y],也即设置的为top和right的值,absolute为true时有效
|
||||
offset: {
|
||||
type: Array,
|
||||
default: uni.$u.props.badge.offset
|
||||
},
|
||||
// 是否反转背景和字体颜色
|
||||
inverted: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.badge.inverted
|
||||
},
|
||||
// 是否绝对定位
|
||||
absolute: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.badge.absolute
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,171 +0,0 @@
|
|||
<template>
|
||||
<text
|
||||
v-if="show && ((Number(value) === 0 ? showZero : true) || isDot)"
|
||||
:class="[isDot ? 'u-badge--dot' : 'u-badge--not-dot', inverted && 'u-badge--inverted', shape === 'horn' && 'u-badge--horn', `u-badge--${type}${inverted ? '--inverted' : ''}`]"
|
||||
:style="[$u.addStyle(customStyle), badgeStyle]"
|
||||
class="u-badge"
|
||||
>{{ isDot ? '' :showValue }}</text>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import props from './props.js';
|
||||
/**
|
||||
* badge 徽标数
|
||||
* @description 该组件一般用于图标右上角显示未读的消息数量,提示用户点击,有圆点和圆包含文字两种形式。
|
||||
* @tutorial https://uviewui.com/components/badge.html
|
||||
*
|
||||
* @property {Boolean} isDot 是否显示圆点 (默认 false )
|
||||
* @property {String | Number} value 显示的内容
|
||||
* @property {Boolean} show 是否显示 (默认 true )
|
||||
* @property {String | Number} max 最大值,超过最大值会显示 '{max}+' (默认999)
|
||||
* @property {String} type 主题类型,error|warning|success|primary (默认 'error' )
|
||||
* @property {Boolean} showZero 当数值为 0 时,是否展示 Badge (默认 false )
|
||||
* @property {String} bgColor 背景颜色,优先级比type高,如设置,type参数会失效
|
||||
* @property {String} color 字体颜色 (默认 '#ffffff' )
|
||||
* @property {String} shape 徽标形状,circle-四角均为圆角,horn-左下角为直角 (默认 'circle' )
|
||||
* @property {String} numberType 设置数字的显示方式,overflow|ellipsis|limit (默认 'overflow' )
|
||||
* @property {Array}} offset 设置badge的位置偏移,格式为 [x, y],也即设置的为top和right的值,absolute为true时有效
|
||||
* @property {Boolean} inverted 是否反转背景和字体颜色(默认 false )
|
||||
* @property {Boolean} absolute 是否绝对定位(默认 false )
|
||||
* @property {Object} customStyle 定义需要用到的外部样式
|
||||
* @example <u-badge :type="type" :count="count"></u-badge>
|
||||
*/
|
||||
export default {
|
||||
name: 'u-badge',
|
||||
mixins: [uni.$u.mpMixin, props, uni.$u.mixin],
|
||||
computed: {
|
||||
// 是否将badge中心与父组件右上角重合
|
||||
boxStyle() {
|
||||
let style = {};
|
||||
return style;
|
||||
},
|
||||
// 整个组件的样式
|
||||
badgeStyle() {
|
||||
const style = {}
|
||||
if(this.color) {
|
||||
style.color = this.color
|
||||
}
|
||||
if (this.bgColor && !this.inverted) {
|
||||
style.backgroundColor = this.bgColor
|
||||
}
|
||||
if (this.absolute) {
|
||||
style.position = 'absolute'
|
||||
// 如果有设置offset参数
|
||||
if(this.offset.length) {
|
||||
// top和right分为为offset的第一个和第二个值,如果没有第二个值,则right等于top
|
||||
const top = this.offset[0]
|
||||
const right = this.offset[1] || top
|
||||
style.top = uni.$u.addUnit(top)
|
||||
style.right = uni.$u.addUnit(right)
|
||||
}
|
||||
}
|
||||
return style
|
||||
},
|
||||
showValue() {
|
||||
switch (this.numberType) {
|
||||
case "overflow":
|
||||
return Number(this.value) > Number(this.max) ? this.max + "+" : this.value
|
||||
break;
|
||||
case "ellipsis":
|
||||
return Number(this.value) > Number(this.max) ? "..." : this.value
|
||||
break;
|
||||
case "limit":
|
||||
return Number(this.value) > 999 ? Number(this.value) >= 9999 ?
|
||||
Math.floor(this.value / 1e4 * 100) / 100 + "w" : Math.floor(this.value /
|
||||
1e3 * 100) / 100 + "k" : this.value
|
||||
break;
|
||||
default:
|
||||
return Number(this.value)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/components.scss";
|
||||
|
||||
$u-badge-primary: $u-primary !default;
|
||||
$u-badge-error: $u-error !default;
|
||||
$u-badge-success: $u-success !default;
|
||||
$u-badge-info: $u-info !default;
|
||||
$u-badge-warning: $u-warning !default;
|
||||
$u-badge-dot-radius: 100px !default;
|
||||
$u-badge-dot-size: 8px !default;
|
||||
$u-badge-dot-right: 4px !default;
|
||||
$u-badge-dot-top: 0 !default;
|
||||
$u-badge-text-font-size: 11px !default;
|
||||
$u-badge-text-right: 10px !default;
|
||||
$u-badge-text-padding: 2px 5px !default;
|
||||
$u-badge-text-align: center !default;
|
||||
$u-badge-text-color: #FFFFFF !default;
|
||||
|
||||
.u-badge {
|
||||
border-top-right-radius: $u-badge-dot-radius;
|
||||
border-top-left-radius: $u-badge-dot-radius;
|
||||
border-bottom-left-radius: $u-badge-dot-radius;
|
||||
border-bottom-right-radius: $u-badge-dot-radius;
|
||||
@include flex;
|
||||
line-height: $u-badge-text-font-size;
|
||||
text-align: $u-badge-text-align;
|
||||
font-size: $u-badge-text-font-size;
|
||||
color: $u-badge-text-color;
|
||||
|
||||
&--dot {
|
||||
height: $u-badge-dot-size;
|
||||
width: $u-badge-dot-size;
|
||||
}
|
||||
|
||||
&--inverted {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
&--not-dot {
|
||||
padding: $u-badge-text-padding;
|
||||
}
|
||||
|
||||
&--horn {
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
|
||||
&--primary {
|
||||
background-color: $u-badge-primary;
|
||||
}
|
||||
|
||||
&--primary--inverted {
|
||||
color: $u-badge-primary;
|
||||
}
|
||||
|
||||
&--error {
|
||||
background-color: $u-badge-error;
|
||||
}
|
||||
|
||||
&--error--inverted {
|
||||
color: $u-badge-error;
|
||||
}
|
||||
|
||||
&--success {
|
||||
background-color: $u-badge-success;
|
||||
}
|
||||
|
||||
&--success--inverted {
|
||||
color: $u-badge-success;
|
||||
}
|
||||
|
||||
&--info {
|
||||
background-color: $u-badge-info;
|
||||
}
|
||||
|
||||
&--info--inverted {
|
||||
color: $u-badge-info;
|
||||
}
|
||||
|
||||
&--warning {
|
||||
background-color: $u-badge-warning;
|
||||
}
|
||||
|
||||
&--warning--inverted {
|
||||
color: $u-badge-warning;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,46 +0,0 @@
|
|||
$u-button-active-opacity:0.75 !default;
|
||||
$u-button-loading-text-margin-left:4px !default;
|
||||
$u-button-text-color: #FFFFFF !default;
|
||||
$u-button-text-plain-error-color:$u-error !default;
|
||||
$u-button-text-plain-warning-color:$u-warning !default;
|
||||
$u-button-text-plain-success-color:$u-success !default;
|
||||
$u-button-text-plain-info-color:$u-info !default;
|
||||
$u-button-text-plain-primary-color:$u-primary !default;
|
||||
.u-button {
|
||||
&--active {
|
||||
opacity: $u-button-active-opacity;
|
||||
}
|
||||
|
||||
&--active--plain {
|
||||
background-color: rgb(217, 217, 217);
|
||||
}
|
||||
|
||||
&__loading-text {
|
||||
margin-left:$u-button-loading-text-margin-left;
|
||||
}
|
||||
|
||||
&__text,
|
||||
&__loading-text {
|
||||
color:$u-button-text-color;
|
||||
}
|
||||
|
||||
&__text--plain--error {
|
||||
color:$u-button-text-plain-error-color;
|
||||
}
|
||||
|
||||
&__text--plain--warning {
|
||||
color:$u-button-text-plain-warning-color;
|
||||
}
|
||||
|
||||
&__text--plain--success{
|
||||
color:$u-button-text-plain-success-color;
|
||||
}
|
||||
|
||||
&__text--plain--info {
|
||||
color:$u-button-text-plain-info-color;
|
||||
}
|
||||
|
||||
&__text--plain--primary {
|
||||
color:$u-button-text-plain-primary-color;
|
||||
}
|
||||
}
|
|
@ -1,161 +0,0 @@
|
|||
/*
|
||||
* @Author : LQ
|
||||
* @Description :
|
||||
* @version : 1.0
|
||||
* @Date : 2021-08-16 10:04:04
|
||||
* @LastAuthor : LQ
|
||||
* @lastTime : 2021-08-16 10:04:24
|
||||
* @FilePath : /u-view2.0/uview-ui/components/u-button/props.js
|
||||
*/
|
||||
export default {
|
||||
props: {
|
||||
// 是否细边框
|
||||
hairline: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.button.hairline
|
||||
},
|
||||
// 按钮的预置样式,info,primary,error,warning,success
|
||||
type: {
|
||||
type: String,
|
||||
default: uni.$u.props.button.type
|
||||
},
|
||||
// 按钮尺寸,large,normal,small,mini
|
||||
size: {
|
||||
type: String,
|
||||
default: uni.$u.props.button.size
|
||||
},
|
||||
// 按钮形状,circle(两边为半圆),square(带圆角)
|
||||
shape: {
|
||||
type: String,
|
||||
default: uni.$u.props.button.shape
|
||||
},
|
||||
// 按钮是否镂空
|
||||
plain: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.button.plain
|
||||
},
|
||||
// 是否禁止状态
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.button.disabled
|
||||
},
|
||||
// 是否加载中
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.button.loading
|
||||
},
|
||||
// 加载中提示文字
|
||||
loadingText: {
|
||||
type: [String, Number],
|
||||
default: uni.$u.props.button.loadingText
|
||||
},
|
||||
// 加载状态图标类型
|
||||
loadingMode: {
|
||||
type: String,
|
||||
default: uni.$u.props.button.loadingMode
|
||||
},
|
||||
// 加载图标大小
|
||||
loadingSize: {
|
||||
type: [String, Number],
|
||||
default: uni.$u.props.button.loadingSize
|
||||
},
|
||||
// 开放能力,具体请看uniapp稳定关于button组件部分说明
|
||||
// https://uniapp.dcloud.io/component/button
|
||||
openType: {
|
||||
type: String,
|
||||
default: uni.$u.props.button.openType
|
||||
},
|
||||
// 用于 <form> 组件,点击分别会触发 <form> 组件的 submit/reset 事件
|
||||
// 取值为submit(提交表单),reset(重置表单)
|
||||
formType: {
|
||||
type: String,
|
||||
default: uni.$u.props.button.formType
|
||||
},
|
||||
// 打开 APP 时,向 APP 传递的参数,open-type=launchApp时有效
|
||||
// 只微信小程序、QQ小程序有效
|
||||
appParameter: {
|
||||
type: String,
|
||||
default: uni.$u.props.button.appParameter
|
||||
},
|
||||
// 指定是否阻止本节点的祖先节点出现点击态,微信小程序有效
|
||||
hoverStopPropagation: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.button.hoverStopPropagation
|
||||
},
|
||||
// 指定返回用户信息的语言,zh_CN 简体中文,zh_TW 繁体中文,en 英文。只微信小程序有效
|
||||
lang: {
|
||||
type: String,
|
||||
default: uni.$u.props.button.lang
|
||||
},
|
||||
// 会话来源,open-type="contact"时有效。只微信小程序有效
|
||||
sessionFrom: {
|
||||
type: String,
|
||||
default: uni.$u.props.button.sessionFrom
|
||||
},
|
||||
// 会话内消息卡片标题,open-type="contact"时有效
|
||||
// 默认当前标题,只微信小程序有效
|
||||
sendMessageTitle: {
|
||||
type: String,
|
||||
default: uni.$u.props.button.sendMessageTitle
|
||||
},
|
||||
// 会话内消息卡片点击跳转小程序路径,open-type="contact"时有效
|
||||
// 默认当前分享路径,只微信小程序有效
|
||||
sendMessagePath: {
|
||||
type: String,
|
||||
default: uni.$u.props.button.sendMessagePath
|
||||
},
|
||||
// 会话内消息卡片图片,open-type="contact"时有效
|
||||
// 默认当前页面截图,只微信小程序有效
|
||||
sendMessageImg: {
|
||||
type: String,
|
||||
default: uni.$u.props.button.sendMessageImg
|
||||
},
|
||||
// 是否显示会话内消息卡片,设置此参数为 true,用户进入客服会话会在右下角显示"可能要发送的小程序"提示,
|
||||
// 用户点击后可以快速发送小程序消息,open-type="contact"时有效
|
||||
showMessageCard: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.button.showMessageCard
|
||||
},
|
||||
// 额外传参参数,用于小程序的data-xxx属性,通过target.dataset.name获取
|
||||
dataName: {
|
||||
type: String,
|
||||
default: uni.$u.props.button.dataName
|
||||
},
|
||||
// 节流,一定时间内只能触发一次
|
||||
throttleTime: {
|
||||
type: [String, Number],
|
||||
default: uni.$u.props.button.throttleTime
|
||||
},
|
||||
// 按住后多久出现点击态,单位毫秒
|
||||
hoverStartTime: {
|
||||
type: [String, Number],
|
||||
default: uni.$u.props.button.hoverStartTime
|
||||
},
|
||||
// 手指松开后点击态保留时间,单位毫秒
|
||||
hoverStayTime: {
|
||||
type: [String, Number],
|
||||
default: uni.$u.props.button.hoverStayTime
|
||||
},
|
||||
// 按钮文字,之所以通过props传入,是因为slot传入的话
|
||||
// nvue中无法控制文字的样式
|
||||
text: {
|
||||
type: [String, Number],
|
||||
default: uni.$u.props.button.text
|
||||
},
|
||||
// 按钮图标
|
||||
icon: {
|
||||
type: String,
|
||||
default: uni.$u.props.button.icon
|
||||
},
|
||||
// 按钮图标
|
||||
iconColor: {
|
||||
type: String,
|
||||
default: uni.$u.props.button.icon
|
||||
},
|
||||
// 按钮颜色,支持传入linear-gradient渐变色
|
||||
color: {
|
||||
type: String,
|
||||
default: uni.$u.props.button.color
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,490 +0,0 @@
|
|||
<template>
|
||||
<!-- #ifndef APP-NVUE -->
|
||||
<button
|
||||
:hover-start-time="Number(hoverStartTime)"
|
||||
:hover-stay-time="Number(hoverStayTime)"
|
||||
:form-type="formType"
|
||||
:open-type="openType"
|
||||
:app-parameter="appParameter"
|
||||
:hover-stop-propagation="hoverStopPropagation"
|
||||
:send-message-title="sendMessageTitle"
|
||||
:send-message-path="sendMessagePath"
|
||||
:lang="lang"
|
||||
:data-name="dataName"
|
||||
:session-from="sessionFrom"
|
||||
:send-message-img="sendMessageImg"
|
||||
:show-message-card="showMessageCard"
|
||||
@getphonenumber="getphonenumber"
|
||||
@getuserinfo="getuserinfo"
|
||||
@error="error"
|
||||
@opensetting="opensetting"
|
||||
@launchapp="launchapp"
|
||||
:hover-class="!disabled && !loading ? 'u-button--active' : ''"
|
||||
class="u-button u-reset-button"
|
||||
:style="[baseColor, $u.addStyle(customStyle)]"
|
||||
@tap="clickHandler"
|
||||
:class="bemClass"
|
||||
>
|
||||
<template v-if="loading">
|
||||
<u-loading-icon
|
||||
:mode="loadingMode"
|
||||
:size="textSize * 1.15"
|
||||
:color="loadingColor"
|
||||
></u-loading-icon>
|
||||
<text
|
||||
class="u-button__loading-text"
|
||||
:style="[{ fontSize: textSize + 'px' }]"
|
||||
>{{ loadingText || text }}</text
|
||||
>
|
||||
</template>
|
||||
<template v-else>
|
||||
<u-icon
|
||||
v-if="icon"
|
||||
:name="icon"
|
||||
:color="iconColorCom"
|
||||
:size="textSize * 1.35"
|
||||
:customStyle="{ marginRight: '2px' }"
|
||||
></u-icon>
|
||||
<slot>
|
||||
<text
|
||||
class="u-button__text"
|
||||
:style="[{ fontSize: textSize + 'px' }]"
|
||||
>{{ text }}</text
|
||||
>
|
||||
</slot>
|
||||
</template>
|
||||
</button>
|
||||
<!-- #endif -->
|
||||
|
||||
<!-- #ifdef APP-NVUE -->
|
||||
<view
|
||||
:hover-start-time="Number(hoverStartTime)"
|
||||
:hover-stay-time="Number(hoverStayTime)"
|
||||
class="u-button"
|
||||
:hover-class="
|
||||
!disabled && !loading && !color && (plain || type === 'info')
|
||||
? 'u-button--active--plain'
|
||||
: !disabled && !loading && !plain
|
||||
? 'u-button--active'
|
||||
: ''
|
||||
"
|
||||
@tap="clickHandler"
|
||||
:class="bemClass"
|
||||
:style="[baseColor, $u.addStyle(customStyle)]"
|
||||
>
|
||||
<template v-if="loading">
|
||||
<u-loading-icon
|
||||
:mode="loadingMode"
|
||||
:size="textSize * 1.15"
|
||||
:color="loadingColor"
|
||||
></u-loading-icon>
|
||||
<text
|
||||
class="u-button__loading-text"
|
||||
:style="[nvueTextStyle]"
|
||||
:class="[plain && `u-button__text--plain--${type}`]"
|
||||
>{{ loadingText || text }}</text
|
||||
>
|
||||
</template>
|
||||
<template v-else>
|
||||
<u-icon
|
||||
v-if="icon"
|
||||
:name="icon"
|
||||
:color="iconColorCom"
|
||||
:size="textSize * 1.35"
|
||||
></u-icon>
|
||||
<text
|
||||
class="u-button__text"
|
||||
:style="[
|
||||
{
|
||||
marginLeft: icon ? '2px' : 0,
|
||||
},
|
||||
nvueTextStyle,
|
||||
]"
|
||||
:class="[plain && `u-button__text--plain--${type}`]"
|
||||
>{{ text }}</text
|
||||
>
|
||||
</template>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import button from "../../libs/mixin/button.js";
|
||||
import openType from "../../libs/mixin/openType.js";
|
||||
import props from "./props.js";
|
||||
/**
|
||||
* button 按钮
|
||||
* @description Button 按钮
|
||||
* @tutorial https://www.uviewui.com/components/button.html
|
||||
*
|
||||
* @property {Boolean} hairline 是否显示按钮的细边框 (默认 true )
|
||||
* @property {String} type 按钮的预置样式,info,primary,error,warning,success (默认 'info' )
|
||||
* @property {String} size 按钮尺寸,large,normal,mini (默认 normal)
|
||||
* @property {String} shape 按钮形状,circle(两边为半圆),square(带圆角) (默认 'square' )
|
||||
* @property {Boolean} plain 按钮是否镂空,背景色透明 (默认 false)
|
||||
* @property {Boolean} disabled 是否禁用 (默认 false)
|
||||
* @property {Boolean} loading 按钮名称前是否带 loading 图标(App-nvue 平台,在 ios 上为雪花,Android上为圆圈) (默认 false)
|
||||
* @property {String | Number} loadingText 加载中提示文字
|
||||
* @property {String} loadingMode 加载状态图标类型 (默认 'spinner' )
|
||||
* @property {String | Number} loadingSize 加载图标大小 (默认 15 )
|
||||
* @property {String} openType 开放能力,具体请看uniapp稳定关于button组件部分说明
|
||||
* @property {String} formType 用于 <form> 组件,点击分别会触发 <form> 组件的 submit/reset 事件
|
||||
* @property {String} appParameter 打开 APP 时,向 APP 传递的参数,open-type=launchApp时有效 (注:只微信小程序、QQ小程序有效)
|
||||
* @property {Boolean} hoverStopPropagation 指定是否阻止本节点的祖先节点出现点击态,微信小程序有效(默认 true )
|
||||
* @property {String} lang 指定返回用户信息的语言,zh_CN 简体中文,zh_TW 繁体中文,en 英文(默认 en )
|
||||
* @property {String} sessionFrom 会话来源,openType="contact"时有效
|
||||
* @property {String} sendMessageTitle 会话内消息卡片标题,openType="contact"时有效
|
||||
* @property {String} sendMessagePath 会话内消息卡片点击跳转小程序路径,openType="contact"时有效
|
||||
* @property {String} sendMessageImg 会话内消息卡片图片,openType="contact"时有效
|
||||
* @property {Boolean} showMessageCard 是否显示会话内消息卡片,设置此参数为 true,用户进入客服会话会在右下角显示"可能要发送的小程序"提示,用户点击后可以快速发送小程序消息,openType="contact"时有效(默认false)
|
||||
* @property {String} dataName 额外传参参数,用于小程序的data-xxx属性,通过target.dataset.name获取
|
||||
* @property {String | Number} throttleTime 节流,一定时间内只能触发一次 (默认 0 )
|
||||
* @property {String | Number} hoverStartTime 按住后多久出现点击态,单位毫秒 (默认 0 )
|
||||
* @property {String | Number} hoverStayTime 手指松开后点击态保留时间,单位毫秒 (默认 200 )
|
||||
* @property {String | Number} text 按钮文字,之所以通过props传入,是因为slot传入的话(注:nvue中无法控制文字的样式)
|
||||
* @property {String} icon 按钮图标
|
||||
* @property {String} iconColor 按钮图标颜色
|
||||
* @property {String} color 按钮颜色,支持传入linear-gradient渐变色
|
||||
* @property {Object} customStyle 定义需要用到的外部样式
|
||||
*
|
||||
* @event {Function} click 非禁止并且非加载中,才能点击
|
||||
* @event {Function} getphonenumber open-type="getPhoneNumber"时有效
|
||||
* @event {Function} getuserinfo 用户点击该按钮时,会返回获取到的用户信息,从返回参数的detail中获取到的值同uni.getUserInfo
|
||||
* @event {Function} error 当使用开放能力时,发生错误的回调
|
||||
* @event {Function} opensetting 在打开授权设置页并关闭后回调
|
||||
* @event {Function} launchapp 打开 APP 成功的回调
|
||||
* @example <u-button>月落</u-button>
|
||||
*/
|
||||
export default {
|
||||
name: "u-button",
|
||||
// #ifdef MP
|
||||
mixins: [uni.$u.mpMixin, uni.$u.mixin, button, openType, props],
|
||||
// #endif
|
||||
// #ifndef MP
|
||||
mixins: [uni.$u.mpMixin, uni.$u.mixin, props],
|
||||
// #endif
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
computed: {
|
||||
// 生成bem风格的类名
|
||||
bemClass() {
|
||||
// this.bem为一个computed变量,在mixin中
|
||||
if (!this.color) {
|
||||
return this.bem(
|
||||
"button",
|
||||
["type", "shape", "size"],
|
||||
["disabled", "plain", "hairline"]
|
||||
);
|
||||
} else {
|
||||
// 由于nvue的原因,在有color参数时,不需要传入type,否则会生成type相关的类型,影响最终的样式
|
||||
return this.bem(
|
||||
"button",
|
||||
["shape", "size"],
|
||||
["disabled", "plain", "hairline"]
|
||||
);
|
||||
}
|
||||
},
|
||||
loadingColor() {
|
||||
if (this.plain) {
|
||||
// 如果有设置color值,则用color值,否则使用type主题颜色
|
||||
return this.color
|
||||
? this.color
|
||||
: uni.$u.config.color[`u-${this.type}`];
|
||||
}
|
||||
if (this.type === "info") {
|
||||
return "#c9c9c9";
|
||||
}
|
||||
return "rgb(200, 200, 200)";
|
||||
},
|
||||
iconColorCom() {
|
||||
// 如果是镂空状态,设置了color就用color值,否则使用主题颜色,
|
||||
// u-icon的color能接受一个主题颜色的值
|
||||
if (this.iconColor) return this.iconColor;
|
||||
if (this.plain) {
|
||||
return this.color ? this.color : this.type;
|
||||
} else {
|
||||
return this.type === "info" ? "#000000" : "#ffffff";
|
||||
}
|
||||
},
|
||||
baseColor() {
|
||||
let style = {};
|
||||
if (this.color) {
|
||||
// 针对自定义了color颜色的情况,镂空状态下,就是用自定义的颜色
|
||||
style.color = this.plain ? this.color : "white";
|
||||
if (!this.plain) {
|
||||
// 非镂空,背景色使用自定义的颜色
|
||||
style["background-color"] = this.color;
|
||||
}
|
||||
if (this.color.indexOf("gradient") !== -1) {
|
||||
// 如果自定义的颜色为渐变色,不显示边框,以及通过backgroundImage设置渐变色
|
||||
// weex文档说明可以写borderWidth的形式,为什么这里需要分开写?
|
||||
// 因为weex是阿里巴巴为了部门业绩考核而做的你懂的东西,所以需要这么写才有效
|
||||
style.borderTopWidth = 0;
|
||||
style.borderRightWidth = 0;
|
||||
style.borderBottomWidth = 0;
|
||||
style.borderLeftWidth = 0;
|
||||
if (!this.plain) {
|
||||
style.backgroundImage = this.color;
|
||||
}
|
||||
} else {
|
||||
// 非渐变色,则设置边框相关的属性
|
||||
style.borderColor = this.color;
|
||||
style.borderWidth = "1px";
|
||||
style.borderStyle = "solid";
|
||||
}
|
||||
}
|
||||
return style;
|
||||
},
|
||||
// nvue版本按钮的字体不会继承父组件的颜色,需要对每一个text组件进行单独的设置
|
||||
nvueTextStyle() {
|
||||
let style = {};
|
||||
// 针对自定义了color颜色的情况,镂空状态下,就是用自定义的颜色
|
||||
if (this.type === "info") {
|
||||
style.color = "#323233";
|
||||
}
|
||||
if (this.color) {
|
||||
style.color = this.plain ? this.color : "white";
|
||||
}
|
||||
style.fontSize = this.textSize + "px";
|
||||
return style;
|
||||
},
|
||||
// 字体大小
|
||||
textSize() {
|
||||
let fontSize = 14,
|
||||
{ size } = this;
|
||||
if (size === "large") fontSize = 16;
|
||||
if (size === "normal") fontSize = 14;
|
||||
if (size === "small") fontSize = 12;
|
||||
if (size === "mini") fontSize = 10;
|
||||
return fontSize;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
clickHandler() {
|
||||
// 非禁止并且非加载中,才能点击
|
||||
if (!this.disabled && !this.loading) {
|
||||
// 进行节流控制,每this.throttle毫秒内,只在开始处执行
|
||||
uni.$u.throttle(() => {
|
||||
this.$emit("click");
|
||||
}, this.throttleTime);
|
||||
}
|
||||
},
|
||||
// 下面为对接uniapp官方按钮开放能力事件回调的对接
|
||||
getphonenumber(res) {
|
||||
this.$emit("getphonenumber", res);
|
||||
},
|
||||
getuserinfo(res) {
|
||||
this.$emit("getuserinfo", res);
|
||||
},
|
||||
error(res) {
|
||||
this.$emit("error", res);
|
||||
},
|
||||
opensetting(res) {
|
||||
this.$emit("opensetting", res);
|
||||
},
|
||||
launchapp(res) {
|
||||
this.$emit("launchapp", res);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/components.scss";
|
||||
|
||||
/* #ifndef APP-NVUE */
|
||||
@import "./vue.scss";
|
||||
/* #endif */
|
||||
|
||||
/* #ifdef APP-NVUE */
|
||||
@import "./nvue.scss";
|
||||
/* #endif */
|
||||
|
||||
$u-button-u-button-height: 40px !default;
|
||||
$u-button-text-font-size: 15px !default;
|
||||
$u-button-loading-text-font-size: 15px !default;
|
||||
$u-button-loading-text-margin-left: 4px !default;
|
||||
$u-button-large-width: 100% !default;
|
||||
$u-button-large-height: 50px !default;
|
||||
$u-button-normal-padding: 0 12px !default;
|
||||
$u-button-large-padding: 0 15px !default;
|
||||
$u-button-normal-font-size: 14px !default;
|
||||
$u-button-small-min-width: 60px !default;
|
||||
$u-button-small-height: 30px !default;
|
||||
$u-button-small-padding: 0px 8px !default;
|
||||
$u-button-mini-padding: 0px 8px !default;
|
||||
$u-button-small-font-size: 12px !default;
|
||||
$u-button-mini-height: 22px !default;
|
||||
$u-button-mini-font-size: 10px !default;
|
||||
$u-button-mini-min-width: 50px !default;
|
||||
$u-button-disabled-opacity: 0.5 !default;
|
||||
$u-button-info-color: #323233 !default;
|
||||
$u-button-info-background-color: #fff !default;
|
||||
$u-button-info-border-color: #ebedf0 !default;
|
||||
$u-button-info-border-width: 1px !default;
|
||||
$u-button-info-border-style: solid !default;
|
||||
$u-button-success-color: #fff !default;
|
||||
$u-button-success-background-color: $u-success !default;
|
||||
$u-button-success-border-color: $u-button-success-background-color !default;
|
||||
$u-button-success-border-width: 1px !default;
|
||||
$u-button-success-border-style: solid !default;
|
||||
$u-button-primary-color: #fff !default;
|
||||
$u-button-primary-background-color: $u-primary !default;
|
||||
$u-button-primary-border-color: $u-button-primary-background-color !default;
|
||||
$u-button-primary-border-width: 1px !default;
|
||||
$u-button-primary-border-style: solid !default;
|
||||
$u-button-error-color: #fff !default;
|
||||
$u-button-error-background-color: $u-error !default;
|
||||
$u-button-error-border-color: $u-button-error-background-color !default;
|
||||
$u-button-error-border-width: 1px !default;
|
||||
$u-button-error-border-style: solid !default;
|
||||
$u-button-warning-color: #fff !default;
|
||||
$u-button-warning-background-color: $u-warning !default;
|
||||
$u-button-warning-border-color: $u-button-warning-background-color !default;
|
||||
$u-button-warning-border-width: 1px !default;
|
||||
$u-button-warning-border-style: solid !default;
|
||||
$u-button-block-width: 100% !default;
|
||||
$u-button-circle-border-top-right-radius: 100px !default;
|
||||
$u-button-circle-border-top-left-radius: 100px !default;
|
||||
$u-button-circle-border-bottom-left-radius: 100px !default;
|
||||
$u-button-circle-border-bottom-right-radius: 100px !default;
|
||||
$u-button-square-border-top-right-radius: 3px !default;
|
||||
$u-button-square-border-top-left-radius: 3px !default;
|
||||
$u-button-square-border-bottom-left-radius: 3px !default;
|
||||
$u-button-square-border-bottom-right-radius: 3px !default;
|
||||
$u-button-icon-min-width: 1em !default;
|
||||
$u-button-plain-background-color: #fff !default;
|
||||
$u-button-hairline-border-width: 0.5px !default;
|
||||
|
||||
.u-button {
|
||||
height: $u-button-u-button-height;
|
||||
position: relative;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@include flex;
|
||||
/* #ifndef APP-NVUE */
|
||||
box-sizing: border-box;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
|
||||
&__text {
|
||||
font-size: $u-button-text-font-size;
|
||||
}
|
||||
|
||||
&__loading-text {
|
||||
font-size: $u-button-loading-text-font-size;
|
||||
margin-left: $u-button-loading-text-margin-left;
|
||||
}
|
||||
|
||||
&--large {
|
||||
/* #ifndef APP-NVUE */
|
||||
width: $u-button-large-width;
|
||||
/* #endif */
|
||||
height: $u-button-large-height;
|
||||
padding: $u-button-large-padding;
|
||||
}
|
||||
|
||||
&--normal {
|
||||
padding: $u-button-normal-padding;
|
||||
font-size: $u-button-normal-font-size;
|
||||
}
|
||||
|
||||
&--small {
|
||||
/* #ifndef APP-NVUE */
|
||||
min-width: $u-button-small-min-width;
|
||||
/* #endif */
|
||||
height: $u-button-small-height;
|
||||
padding: $u-button-small-padding;
|
||||
font-size: $u-button-small-font-size;
|
||||
}
|
||||
|
||||
&--mini {
|
||||
height: $u-button-mini-height;
|
||||
font-size: $u-button-mini-font-size;
|
||||
/* #ifndef APP-NVUE */
|
||||
min-width: $u-button-mini-min-width;
|
||||
/* #endif */
|
||||
padding: $u-button-mini-padding;
|
||||
}
|
||||
|
||||
&--disabled {
|
||||
opacity: $u-button-disabled-opacity;
|
||||
}
|
||||
|
||||
&--info {
|
||||
color: $u-button-info-color;
|
||||
background-color: $u-button-info-background-color;
|
||||
border-color: $u-button-info-border-color;
|
||||
border-width: $u-button-info-border-width;
|
||||
border-style: $u-button-info-border-style;
|
||||
}
|
||||
|
||||
&--success {
|
||||
color: $u-button-success-color;
|
||||
background-color: $u-button-success-background-color;
|
||||
border-color: $u-button-success-border-color;
|
||||
border-width: $u-button-success-border-width;
|
||||
border-style: $u-button-success-border-style;
|
||||
}
|
||||
|
||||
&--primary {
|
||||
color: $u-button-primary-color;
|
||||
background-color: $u-button-primary-background-color;
|
||||
border-color: $u-button-primary-border-color;
|
||||
border-width: $u-button-primary-border-width;
|
||||
border-style: $u-button-primary-border-style;
|
||||
}
|
||||
|
||||
&--error {
|
||||
color: $u-button-error-color;
|
||||
background-color: $u-button-error-background-color;
|
||||
border-color: $u-button-error-border-color;
|
||||
border-width: $u-button-error-border-width;
|
||||
border-style: $u-button-error-border-style;
|
||||
}
|
||||
|
||||
&--warning {
|
||||
color: $u-button-warning-color;
|
||||
background-color: $u-button-warning-background-color;
|
||||
border-color: $u-button-warning-border-color;
|
||||
border-width: $u-button-warning-border-width;
|
||||
border-style: $u-button-warning-border-style;
|
||||
}
|
||||
|
||||
&--block {
|
||||
@include flex;
|
||||
width: $u-button-block-width;
|
||||
}
|
||||
|
||||
&--circle {
|
||||
border-top-right-radius: $u-button-circle-border-top-right-radius;
|
||||
border-top-left-radius: $u-button-circle-border-top-left-radius;
|
||||
border-bottom-left-radius: $u-button-circle-border-bottom-left-radius;
|
||||
border-bottom-right-radius: $u-button-circle-border-bottom-right-radius;
|
||||
}
|
||||
|
||||
&--square {
|
||||
border-bottom-left-radius: $u-button-square-border-top-right-radius;
|
||||
border-bottom-right-radius: $u-button-square-border-top-left-radius;
|
||||
border-top-left-radius: $u-button-square-border-bottom-left-radius;
|
||||
border-top-right-radius: $u-button-square-border-bottom-right-radius;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
/* #ifndef APP-NVUE */
|
||||
min-width: $u-button-icon-min-width;
|
||||
line-height: inherit !important;
|
||||
vertical-align: top;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
&--plain {
|
||||
background-color: $u-button-plain-background-color;
|
||||
}
|
||||
|
||||
&--hairline {
|
||||
border-width: $u-button-hairline-border-width !important;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,80 +0,0 @@
|
|||
// nvue下hover-class无效
|
||||
$u-button-before-top:50% !default;
|
||||
$u-button-before-left:50% !default;
|
||||
$u-button-before-width:100% !default;
|
||||
$u-button-before-height:100% !default;
|
||||
$u-button-before-transform:translate(-50%, -50%) !default;
|
||||
$u-button-before-opacity:0 !default;
|
||||
$u-button-before-background-color:#000 !default;
|
||||
$u-button-before-border-color:#000 !default;
|
||||
$u-button-active-before-opacity:.15 !default;
|
||||
$u-button-icon-margin-left:4px !default;
|
||||
$u-button-plain-u-button-info-color:$u-info;
|
||||
$u-button-plain-u-button-success-color:$u-success;
|
||||
$u-button-plain-u-button-error-color:$u-error;
|
||||
$u-button-plain-u-button-warning-color:$u-error;
|
||||
|
||||
.u-button {
|
||||
width: 100%;
|
||||
|
||||
&__text {
|
||||
white-space: nowrap;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
&:before {
|
||||
position: absolute;
|
||||
top:$u-button-before-top;
|
||||
left:$u-button-before-left;
|
||||
width:$u-button-before-width;
|
||||
height:$u-button-before-height;
|
||||
border: inherit;
|
||||
border-radius: inherit;
|
||||
transform:$u-button-before-transform;
|
||||
opacity:$u-button-before-opacity;
|
||||
content: " ";
|
||||
background-color:$u-button-before-background-color;
|
||||
border-color:$u-button-before-border-color;
|
||||
}
|
||||
|
||||
&--active {
|
||||
&:before {
|
||||
opacity: .15
|
||||
}
|
||||
}
|
||||
|
||||
&__icon+&__text:not(:empty),
|
||||
&__loading-text {
|
||||
margin-left:$u-button-icon-margin-left;
|
||||
}
|
||||
|
||||
&--plain {
|
||||
&.u-button--primary {
|
||||
color: $u-primary;
|
||||
}
|
||||
}
|
||||
|
||||
&--plain {
|
||||
&.u-button--info {
|
||||
color:$u-button-plain-u-button-info-color;
|
||||
}
|
||||
}
|
||||
|
||||
&--plain {
|
||||
&.u-button--success {
|
||||
color:$u-button-plain-u-button-success-color;
|
||||
}
|
||||
}
|
||||
|
||||
&--plain {
|
||||
&.u-button--error {
|
||||
color:$u-button-plain-u-button-error-color;
|
||||
}
|
||||
}
|
||||
|
||||
&--plain {
|
||||
&.u-button--warning {
|
||||
color:$u-button-plain-u-button-warning-color;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,99 +0,0 @@
|
|||
<template>
|
||||
<view class="u-calendar-header u-border-bottom">
|
||||
<text
|
||||
class="u-calendar-header__title"
|
||||
v-if="showTitle"
|
||||
>{{ title }}</text>
|
||||
<text
|
||||
class="u-calendar-header__subtitle"
|
||||
v-if="showSubtitle"
|
||||
>{{ subtitle }}</text>
|
||||
<view class="u-calendar-header__weekdays">
|
||||
<text class="u-calendar-header__weekdays__weekday">一</text>
|
||||
<text class="u-calendar-header__weekdays__weekday">二</text>
|
||||
<text class="u-calendar-header__weekdays__weekday">三</text>
|
||||
<text class="u-calendar-header__weekdays__weekday">四</text>
|
||||
<text class="u-calendar-header__weekdays__weekday">五</text>
|
||||
<text class="u-calendar-header__weekdays__weekday">六</text>
|
||||
<text class="u-calendar-header__weekdays__weekday">日</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'u-calendar-header',
|
||||
mixins: [uni.$u.mpMixin, uni.$u.mixin],
|
||||
props: {
|
||||
// 标题
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 副标题
|
||||
subtitle: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 是否显示标题
|
||||
showTitle: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否显示副标题
|
||||
showSubtitle: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
name() {
|
||||
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/components.scss";
|
||||
|
||||
.u-calendar-header {
|
||||
padding-bottom: 4px;
|
||||
|
||||
&__title {
|
||||
font-size: 16px;
|
||||
color: $u-main-color;
|
||||
text-align: center;
|
||||
height: 42px;
|
||||
line-height: 42px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
&__subtitle {
|
||||
font-size: 14px;
|
||||
color: $u-main-color;
|
||||
height: 40px;
|
||||
text-align: center;
|
||||
line-height: 40px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
&__weekdays {
|
||||
@include flex;
|
||||
justify-content: space-between;
|
||||
|
||||
&__weekday {
|
||||
font-size: 13px;
|
||||
color: $u-main-color;
|
||||
line-height: 30px;
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,579 +0,0 @@
|
|||
<template>
|
||||
<view class="u-calendar-month-wrapper" ref="u-calendar-month-wrapper">
|
||||
<view v-for="(item, index) in months" :key="index" :class="[`u-calendar-month-${index}`]"
|
||||
:ref="`u-calendar-month-${index}`" :id="`month-${index}`">
|
||||
<text v-if="index !== 0" class="u-calendar-month__title">{{ item.year }}年{{ item.month }}月</text>
|
||||
<view class="u-calendar-month__days">
|
||||
<view v-if="showMark" class="u-calendar-month__days__month-mark-wrapper">
|
||||
<text class="u-calendar-month__days__month-mark-wrapper__text">{{ item.month }}</text>
|
||||
</view>
|
||||
<view class="u-calendar-month__days__day" v-for="(item1, index1) in item.date" :key="index1"
|
||||
:style="[dayStyle(index, index1, item1)]" @tap="clickHandler(index, index1, item1)"
|
||||
:class="[item1.selected && 'u-calendar-month__days__day__select--selected']">
|
||||
<view class="u-calendar-month__days__day__select" :style="[daySelectStyle(index, index1, item1)]">
|
||||
<text class="u-calendar-month__days__day__select__info"
|
||||
:class="[item1.disabled && 'u-calendar-month__days__day__select__info--disabled']"
|
||||
:style="[textStyle(item1)]">{{ item1.day }}</text>
|
||||
<text v-if="getBottomInfo(index, index1, item1)"
|
||||
class="u-calendar-month__days__day__select__buttom-info"
|
||||
:class="[item1.disabled && 'u-calendar-month__days__day__select__buttom-info--disabled']"
|
||||
:style="[textStyle(item1)]">{{ getBottomInfo(index, index1, item1) }}</text>
|
||||
<text v-if="item1.dot" class="u-calendar-month__days__day__select__dot"></text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// #ifdef APP-NVUE
|
||||
// 由于nvue不支持百分比单位,需要查询宽度来计算每个日期的宽度
|
||||
const dom = uni.requireNativePlugin('dom')
|
||||
// #endif
|
||||
import dayjs from '../../libs/util/dayjs.js';
|
||||
export default {
|
||||
name: 'u-calendar-month',
|
||||
mixins: [uni.$u.mpMixin, uni.$u.mixin],
|
||||
props: {
|
||||
// 是否显示月份背景色
|
||||
showMark: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 主题色,对底部按钮和选中日期有效
|
||||
color: {
|
||||
type: String,
|
||||
default: '#3c9cff'
|
||||
},
|
||||
// 月份数据
|
||||
months: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
// 日期选择类型
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'single'
|
||||
},
|
||||
// 日期行高
|
||||
rowHeight: {
|
||||
type: [String, Number],
|
||||
default: 58
|
||||
},
|
||||
// mode=multiple时,最多可选多少个日期
|
||||
maxCount: {
|
||||
type: [String, Number],
|
||||
default: Infinity
|
||||
},
|
||||
// mode=range时,第一个日期底部的提示文字
|
||||
startText: {
|
||||
type: String,
|
||||
default: '开始'
|
||||
},
|
||||
// mode=range时,最后一个日期底部的提示文字
|
||||
endText: {
|
||||
type: String,
|
||||
default: '结束'
|
||||
},
|
||||
// 默认选中的日期,mode为multiple或range是必须为数组格式
|
||||
defaultDate: {
|
||||
type: [Array, String, Date],
|
||||
default: null
|
||||
},
|
||||
// 最小的可选日期
|
||||
minDate: {
|
||||
type: [String, Number],
|
||||
default: 0
|
||||
},
|
||||
// 最大可选日期
|
||||
maxDate: {
|
||||
type: [String, Number],
|
||||
default: 0
|
||||
},
|
||||
// 如果没有设置maxDate,则往后推多少个月
|
||||
maxMonth: {
|
||||
type: [String, Number],
|
||||
default: 2
|
||||
},
|
||||
// 是否为只读状态,只读状态下禁止选择日期
|
||||
readonly: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.calendar.readonly
|
||||
},
|
||||
// 日期区间最多可选天数,默认无限制,mode = range时有效
|
||||
maxRange: {
|
||||
type: [Number, String],
|
||||
default: Infinity
|
||||
},
|
||||
// 范围选择超过最多可选天数时的提示文案,mode = range时有效
|
||||
rangePrompt: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 范围选择超过最多可选天数时,是否展示提示文案,mode = range时有效
|
||||
showRangePrompt: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否允许日期范围的起止时间为同一天,mode = range时有效
|
||||
allowSameDay: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 每个日期的宽度
|
||||
width: 0,
|
||||
// 当前选中的日期item
|
||||
item: {},
|
||||
selected: []
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
selectedChange: {
|
||||
immediate: true,
|
||||
handler(n) {
|
||||
this.setDefaultDate()
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 多个条件的变化,会引起选中日期的变化,这里统一管理监听
|
||||
selectedChange() {
|
||||
return [this.minDate, this.maxDate, this.defaultDate]
|
||||
},
|
||||
dayStyle(index1, index2, item) {
|
||||
return (index1, index2, item) => {
|
||||
const style = {}
|
||||
let week = item.week
|
||||
// 不进行四舍五入的形式保留2位小数
|
||||
const dayWidth = Number(parseFloat(this.width / 7).toFixed(3).slice(0, -1))
|
||||
// 得出每个日期的宽度
|
||||
// #ifdef APP-NVUE
|
||||
style.width = uni.$u.addUnit(dayWidth)
|
||||
// #endif
|
||||
style.height = uni.$u.addUnit(this.rowHeight)
|
||||
if (index2 === 0) {
|
||||
// 获取当前为星期几,如果为0,则为星期天,减一为每月第一天时,需要向左偏移的item个数
|
||||
week = (week === 0 ? 7 : week) - 1
|
||||
style.marginLeft = uni.$u.addUnit(week * dayWidth)
|
||||
}
|
||||
if (this.mode === 'range') {
|
||||
// 之所以需要这么写,是因为DCloud公司的iOS客户端的开发者能力有限导致的bug
|
||||
style.paddingLeft = 0
|
||||
style.paddingRight = 0
|
||||
style.paddingBottom = 0
|
||||
style.paddingTop = 0
|
||||
}
|
||||
return style
|
||||
}
|
||||
},
|
||||
daySelectStyle() {
|
||||
return (index1, index2, item) => {
|
||||
let date = dayjs(item.date).format("YYYY-MM-DD"),
|
||||
style = {}
|
||||
// 判断date是否在selected数组中,因为月份可能会需要补0,所以使用dateSame判断,而不用数组的includes判断
|
||||
if (this.selected.some(item => this.dateSame(item, date))) {
|
||||
style.backgroundColor = this.color
|
||||
}
|
||||
if (this.mode === 'single') {
|
||||
if (date === this.selected[0]) {
|
||||
// 因为需要对nvue的兼容,只能这么写,无法缩写,也无法通过类名控制等等
|
||||
style.borderTopLeftRadius = '3px'
|
||||
style.borderBottomLeftRadius = '3px'
|
||||
style.borderTopRightRadius = '3px'
|
||||
style.borderBottomRightRadius = '3px'
|
||||
}
|
||||
} else if (this.mode === 'range') {
|
||||
if (this.selected.length >= 2) {
|
||||
const len = this.selected.length - 1
|
||||
// 第一个日期设置左上角和左下角的圆角
|
||||
if (this.dateSame(date, this.selected[0])) {
|
||||
style.borderTopLeftRadius = '3px'
|
||||
style.borderBottomLeftRadius = '3px'
|
||||
}
|
||||
// 最后一个日期设置右上角和右下角的圆角
|
||||
if (this.dateSame(date, this.selected[len])) {
|
||||
style.borderTopRightRadius = '3px'
|
||||
style.borderBottomRightRadius = '3px'
|
||||
}
|
||||
// 处于第一和最后一个之间的日期,背景色设置为浅色,通过将对应颜色进行等分,再取其尾部的颜色值
|
||||
if (dayjs(date).isAfter(dayjs(this.selected[0])) && dayjs(date).isBefore(dayjs(this
|
||||
.selected[len]))) {
|
||||
style.backgroundColor = uni.$u.colorGradient(this.color, '#ffffff', 100)[90]
|
||||
// 增加一个透明度,让范围区间的背景色也能看到底部的mark水印字符
|
||||
style.opacity = 0.7
|
||||
}
|
||||
} else if (this.selected.length === 1) {
|
||||
// 之所以需要这么写,是因为DCloud公司的iOS客户端的开发者能力有限导致的bug
|
||||
// 进行还原操作,否则在nvue的iOS,uni-app有bug,会导致诡异的表现
|
||||
style.borderTopLeftRadius = '3px'
|
||||
style.borderBottomLeftRadius = '3px'
|
||||
}
|
||||
} else {
|
||||
if (this.selected.some(item => this.dateSame(item, date))) {
|
||||
style.borderTopLeftRadius = '3px'
|
||||
style.borderBottomLeftRadius = '3px'
|
||||
style.borderTopRightRadius = '3px'
|
||||
style.borderBottomRightRadius = '3px'
|
||||
}
|
||||
}
|
||||
return style
|
||||
}
|
||||
},
|
||||
// 某个日期是否被选中
|
||||
textStyle() {
|
||||
return (item) => {
|
||||
const date = dayjs(item.date).format("YYYY-MM-DD"),
|
||||
style = {}
|
||||
// 选中的日期,提示文字设置白色
|
||||
if (this.selected.some(item => this.dateSame(item, date))) {
|
||||
style.color = '#ffffff'
|
||||
}
|
||||
if (this.mode === 'range') {
|
||||
const len = this.selected.length - 1
|
||||
// 如果是范围选择模式,第一个和最后一个之间的日期,文字颜色设置为高亮的主题色
|
||||
if (dayjs(date).isAfter(dayjs(this.selected[0])) && dayjs(date).isBefore(dayjs(this
|
||||
.selected[len]))) {
|
||||
style.color = this.color
|
||||
}
|
||||
}
|
||||
return style
|
||||
}
|
||||
},
|
||||
// 获取底部的提示文字
|
||||
getBottomInfo() {
|
||||
return (index1, index2, item) => {
|
||||
const date = dayjs(item.date).format("YYYY-MM-DD")
|
||||
const bottomInfo = item.bottomInfo
|
||||
// 当为日期范围模式时,且选择的日期个数大于0时
|
||||
if (this.mode === 'range' && this.selected.length > 0) {
|
||||
if (this.selected.length === 1) {
|
||||
// 选择了一个日期时,如果当前日期为数组中的第一个日期,则显示底部文字为“开始”
|
||||
if (this.dateSame(date, this.selected[0])) return this.startText
|
||||
else return bottomInfo
|
||||
} else {
|
||||
const len = this.selected.length - 1
|
||||
// 如果数组中的日期大于2个时,第一个和最后一个显示为开始和结束日期
|
||||
if (this.dateSame(date, this.selected[0]) && this.dateSame(date, this.selected[1]) &&
|
||||
len === 1) {
|
||||
// 如果长度为2,且第一个等于第二个日期,则提示语放在同一个item中
|
||||
return `${this.startText}/${this.endText}`
|
||||
} else if (this.dateSame(date, this.selected[0])) {
|
||||
return this.startText
|
||||
} else if (this.dateSame(date, this.selected[len])) {
|
||||
return this.endText
|
||||
} else {
|
||||
return bottomInfo
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return bottomInfo
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.init()
|
||||
},
|
||||
methods: {
|
||||
init() {
|
||||
// 初始化默认选中
|
||||
this.$emit('monthSelected', this.selected)
|
||||
this.$nextTick(() => {
|
||||
// 这里需要另一个延时,因为获取宽度后,会进行月份数据渲染,只有渲染完成之后,才有真正的高度
|
||||
// 因为nvue下,$nextTick并不是100%可靠的
|
||||
uni.$u.sleep(10).then(() => {
|
||||
this.getWrapperWidth()
|
||||
this.getMonthRect()
|
||||
})
|
||||
})
|
||||
},
|
||||
// 判断两个日期是否相等
|
||||
dateSame(date1, date2) {
|
||||
return dayjs(date1).isSame(dayjs(date2))
|
||||
},
|
||||
// 获取月份数据区域的宽度,因为nvue不支持百分比,所以无法通过css设置每个日期item的宽度
|
||||
getWrapperWidth() {
|
||||
// #ifdef APP-NVUE
|
||||
dom.getComponentRect(this.$refs['u-calendar-month-wrapper'], res => {
|
||||
this.width = res.size.width
|
||||
})
|
||||
// #endif
|
||||
// #ifndef APP-NVUE
|
||||
this.$uGetRect('.u-calendar-month-wrapper').then(size => {
|
||||
this.width = size.width
|
||||
})
|
||||
// #endif
|
||||
},
|
||||
getMonthRect() {
|
||||
// 获取每个月份数据的尺寸,用于父组件在scroll-view滚动事件中,监听当前滚动到了第几个月份
|
||||
const promiseAllArr = this.months.map((item, index) => this.getMonthRectByPromise(
|
||||
`u-calendar-month-${index}`))
|
||||
// 一次性返回
|
||||
Promise.all(promiseAllArr).then(
|
||||
sizes => {
|
||||
let height = 1
|
||||
const topArr = []
|
||||
for (let i = 0; i < this.months.length; i++) {
|
||||
// 添加到months数组中,供scroll-view滚动事件中,判断当前滚动到哪个月份
|
||||
topArr[i] = height
|
||||
height += sizes[i].height
|
||||
}
|
||||
// 由于微信下,无法通过this.months[i].top的形式(引用类型)去修改父组件的month的top值,所以使用事件形式对外发出
|
||||
this.$emit('updateMonthTop', topArr)
|
||||
})
|
||||
},
|
||||
// 获取每个月份区域的尺寸
|
||||
getMonthRectByPromise(el) {
|
||||
// #ifndef APP-NVUE
|
||||
// $uGetRect为uView自带的节点查询简化方法,详见文档介绍:https://www.uviewui.com/js/getRect.html
|
||||
// 组件内部一般用this.$uGetRect,对外的为uni.$u.getRect,二者功能一致,名称不同
|
||||
return new Promise(resolve => {
|
||||
this.$uGetRect(`.${el}`).then(size => {
|
||||
resolve(size)
|
||||
})
|
||||
})
|
||||
// #endif
|
||||
|
||||
// #ifdef APP-NVUE
|
||||
// nvue下,使用dom模块查询元素高度
|
||||
// 返回一个promise,让调用此方法的主体能使用then回调
|
||||
return new Promise(resolve => {
|
||||
dom.getComponentRect(this.$refs[el][0], res => {
|
||||
resolve(res.size)
|
||||
})
|
||||
})
|
||||
// #endif
|
||||
},
|
||||
// 点击某一个日期
|
||||
clickHandler(index1, index2, item) {
|
||||
if (this.readonly) {
|
||||
return;
|
||||
}
|
||||
this.item = item
|
||||
const date = dayjs(item.date).format("YYYY-MM-DD")
|
||||
if (item.disabled) return
|
||||
// 对上一次选择的日期数组进行深度克隆
|
||||
let selected = uni.$u.deepClone(this.selected)
|
||||
if (this.mode === 'single') {
|
||||
// 单选情况下,让数组中的元素为当前点击的日期
|
||||
selected = [date]
|
||||
} else if (this.mode === 'multiple') {
|
||||
if (selected.some(item => this.dateSame(item, date))) {
|
||||
// 如果点击的日期已在数组中,则进行移除操作,也就是达到反选的效果
|
||||
const itemIndex = selected.findIndex(item => item === date)
|
||||
selected.splice(itemIndex, 1)
|
||||
} else {
|
||||
// 如果点击的日期不在数组中,且已有的长度小于总可选长度时,则添加到数组中去
|
||||
if (selected.length < this.maxCount) selected.push(date)
|
||||
}
|
||||
} else {
|
||||
// 选择区间形式
|
||||
if (selected.length === 0 || selected.length >= 2) {
|
||||
// 如果原来就为0或者大于2的长度,则当前点击的日期,就是开始日期
|
||||
selected = [date]
|
||||
} else if (selected.length === 1) {
|
||||
// 如果已经选择了开始日期
|
||||
const existsDate = selected[0]
|
||||
// 如果当前选择的日期小于上一次选择的日期,则当前的日期定为开始日期
|
||||
if (dayjs(date).isBefore(existsDate)) {
|
||||
selected = [date]
|
||||
} else if (dayjs(date).isAfter(existsDate)) {
|
||||
// 当前日期减去最大可选的日期天数,如果大于起始时间,则进行提示
|
||||
if(dayjs(dayjs(date).subtract(this.maxRange, 'day')).isAfter(dayjs(selected[0])) && this.showRangePrompt) {
|
||||
if(this.rangePrompt) {
|
||||
uni.$u.toast(this.rangePrompt)
|
||||
} else {
|
||||
uni.$u.toast(`选择天数不能超过 ${this.maxRange} 天`)
|
||||
}
|
||||
return
|
||||
}
|
||||
// 如果当前日期大于已有日期,将当前的添加到数组尾部
|
||||
selected.push(date)
|
||||
const startDate = selected[0]
|
||||
const endDate = selected[1]
|
||||
const arr = []
|
||||
let i = 0
|
||||
do {
|
||||
// 将开始和结束日期之间的日期添加到数组中
|
||||
arr.push(dayjs(startDate).add(i, 'day').format("YYYY-MM-DD"))
|
||||
i++
|
||||
// 累加的日期小于结束日期时,继续下一次的循环
|
||||
} while (dayjs(startDate).add(i, 'day').isBefore(dayjs(endDate)))
|
||||
// 为了一次性修改数组,避免computed中多次触发,这里才用arr变量一次性赋值的方式,同时将最后一个日期添加近来
|
||||
arr.push(endDate)
|
||||
selected = arr
|
||||
} else {
|
||||
// 选择区间时,只有一个日期的情况下,且不允许选择起止为同一天的话,不允许选择自己
|
||||
if (selected[0] === date && !this.allowSameDay) return
|
||||
selected.push(date)
|
||||
}
|
||||
}
|
||||
}
|
||||
this.setSelected(selected)
|
||||
},
|
||||
// 设置默认日期
|
||||
setDefaultDate() {
|
||||
if (!this.defaultDate) {
|
||||
// 如果没有设置默认日期,则将当天日期设置为默认选中的日期
|
||||
const selected = [dayjs().format("YYYY-MM-DD")]
|
||||
return this.setSelected(selected, false)
|
||||
}
|
||||
let defaultDate = []
|
||||
const minDate = this.minDate || dayjs().format("YYYY-MM-DD")
|
||||
const maxDate = this.maxDate || dayjs(minDate).add(this.maxMonth - 1, 'month').format("YYYY-MM-DD")
|
||||
if (this.mode === 'single') {
|
||||
// 单选模式,可以是字符串或数组,Date对象等
|
||||
if (!uni.$u.test.array(this.defaultDate)) {
|
||||
defaultDate = [dayjs(this.defaultDate).format("YYYY-MM-DD")]
|
||||
} else {
|
||||
defaultDate = [this.defaultDate[0]]
|
||||
}
|
||||
} else {
|
||||
// 如果为非数组,则不执行
|
||||
if (!uni.$u.test.array(this.defaultDate)) return
|
||||
defaultDate = this.defaultDate
|
||||
}
|
||||
// 过滤用户传递的默认数组,取出只在可允许最大值与最小值之间的元素
|
||||
defaultDate = defaultDate.filter(item => {
|
||||
return dayjs(item).isAfter(dayjs(minDate).subtract(1, 'day')) && dayjs(item).isBefore(dayjs(
|
||||
maxDate).add(1, 'day'))
|
||||
})
|
||||
this.setSelected(defaultDate, false)
|
||||
},
|
||||
setSelected(selected, event = true) {
|
||||
this.selected = selected
|
||||
event && this.$emit('monthSelected', this.selected)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/components.scss";
|
||||
|
||||
.u-calendar-month-wrapper {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.u-calendar-month {
|
||||
|
||||
&__title {
|
||||
font-size: 14px;
|
||||
line-height: 42px;
|
||||
height: 42px;
|
||||
color: $u-main-color;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
&__days {
|
||||
position: relative;
|
||||
@include flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
&__month-mark-wrapper {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
@include flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
&__text {
|
||||
font-size: 155px;
|
||||
color: rgba(231, 232, 234, 0.83);
|
||||
}
|
||||
}
|
||||
|
||||
&__day {
|
||||
@include flex;
|
||||
padding: 2px;
|
||||
/* #ifndef APP-NVUE */
|
||||
// vue下使用css进行宽度计算,因为某些安卓机会无法进行js获取父元素宽度进行计算得出,会有偏移
|
||||
width: calc(100% / 7);
|
||||
box-sizing: border-box;
|
||||
/* #endif */
|
||||
|
||||
&__select {
|
||||
flex: 1;
|
||||
@include flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
|
||||
&__dot {
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
border-radius: 100px;
|
||||
background-color: $u-error;
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
right: 7px;
|
||||
}
|
||||
|
||||
&__buttom-info {
|
||||
color: $u-content-color;
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
bottom: 5px;
|
||||
font-size: 10px;
|
||||
text-align: center;
|
||||
left: 0;
|
||||
right: 0;
|
||||
|
||||
&--selected {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
&--disabled {
|
||||
color: #cacbcd;
|
||||
}
|
||||
}
|
||||
|
||||
&__info {
|
||||
text-align: center;
|
||||
font-size: 16px;
|
||||
|
||||
&--selected {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
&--disabled {
|
||||
color: #cacbcd;
|
||||
}
|
||||
}
|
||||
|
||||
&--selected {
|
||||
background-color: $u-primary;
|
||||
@include flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
&--range-selected {
|
||||
opacity: 0.3;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
&--range-start-selected {
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
&--range-end-selected {
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,144 +0,0 @@
|
|||
export default {
|
||||
props: {
|
||||
// 日历顶部标题
|
||||
title: {
|
||||
type: String,
|
||||
default: uni.$u.props.calendar.title
|
||||
},
|
||||
// 是否显示标题
|
||||
showTitle: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.calendar.showTitle
|
||||
},
|
||||
// 是否显示副标题
|
||||
showSubtitle: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.calendar.showSubtitle
|
||||
},
|
||||
// 日期类型选择,single-选择单个日期,multiple-可以选择多个日期,range-选择日期范围
|
||||
mode: {
|
||||
type: String,
|
||||
default: uni.$u.props.calendar.mode
|
||||
},
|
||||
// mode=range时,第一个日期底部的提示文字
|
||||
startText: {
|
||||
type: String,
|
||||
default: uni.$u.props.calendar.startText
|
||||
},
|
||||
// mode=range时,最后一个日期底部的提示文字
|
||||
endText: {
|
||||
type: String,
|
||||
default: uni.$u.props.calendar.endText
|
||||
},
|
||||
// 自定义列表
|
||||
customList: {
|
||||
type: Array,
|
||||
default: uni.$u.props.calendar.customList
|
||||
},
|
||||
// 主题色,对底部按钮和选中日期有效
|
||||
color: {
|
||||
type: String,
|
||||
default: uni.$u.props.calendar.color
|
||||
},
|
||||
// 最小的可选日期
|
||||
minDate: {
|
||||
type: [String, Number],
|
||||
default: uni.$u.props.calendar.minDate
|
||||
},
|
||||
// 最大可选日期
|
||||
maxDate: {
|
||||
type: [String, Number],
|
||||
default: uni.$u.props.calendar.maxDate
|
||||
},
|
||||
// 默认选中的日期,mode为multiple或range是必须为数组格式
|
||||
defaultDate: {
|
||||
type: [Array, String, Date, null],
|
||||
default: uni.$u.props.calendar.defaultDate
|
||||
},
|
||||
// mode=multiple时,最多可选多少个日期
|
||||
maxCount: {
|
||||
type: [String, Number],
|
||||
default: uni.$u.props.calendar.maxCount
|
||||
},
|
||||
// 日期行高
|
||||
rowHeight: {
|
||||
type: [String, Number],
|
||||
default: uni.$u.props.calendar.rowHeight
|
||||
},
|
||||
// 日期格式化函数
|
||||
formatter: {
|
||||
type: [Function, null],
|
||||
default: uni.$u.props.calendar.formatter
|
||||
},
|
||||
// 是否显示农历
|
||||
showLunar: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.calendar.showLunar
|
||||
},
|
||||
// 是否显示月份背景色
|
||||
showMark: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.calendar.showMark
|
||||
},
|
||||
// 确定按钮的文字
|
||||
confirmText: {
|
||||
type: String,
|
||||
default: uni.$u.props.calendar.confirmText
|
||||
},
|
||||
// 确认按钮处于禁用状态时的文字
|
||||
confirmDisabledText: {
|
||||
type: String,
|
||||
default: uni.$u.props.calendar.confirmDisabledText
|
||||
},
|
||||
// 是否显示日历弹窗
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.calendar.show
|
||||
},
|
||||
// 是否允许点击遮罩关闭日历
|
||||
closeOnClickOverlay: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.calendar.closeOnClickOverlay
|
||||
},
|
||||
// 是否为只读状态,只读状态下禁止选择日期
|
||||
readonly: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.calendar.readonly
|
||||
},
|
||||
// 是否展示确认按钮
|
||||
showConfirm: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.calendar.showConfirm
|
||||
},
|
||||
// 日期区间最多可选天数,默认无限制,mode = range时有效
|
||||
maxRange: {
|
||||
type: [Number, String],
|
||||
default: uni.$u.props.calendar.maxRange
|
||||
},
|
||||
// 范围选择超过最多可选天数时的提示文案,mode = range时有效
|
||||
rangePrompt: {
|
||||
type: String,
|
||||
default: uni.$u.props.calendar.rangePrompt
|
||||
},
|
||||
// 范围选择超过最多可选天数时,是否展示提示文案,mode = range时有效
|
||||
showRangePrompt: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.calendar.showRangePrompt
|
||||
},
|
||||
// 是否允许日期范围的起止时间为同一天,mode = range时有效
|
||||
allowSameDay: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.calendar.allowSameDay
|
||||
},
|
||||
// 圆角值
|
||||
round: {
|
||||
type: [Boolean, String, Number],
|
||||
default: uni.$u.props.calendar.round
|
||||
},
|
||||
// 最多展示月份数量
|
||||
monthNum: {
|
||||
type: [Number, String],
|
||||
default: 3
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,383 +0,0 @@
|
|||
<template>
|
||||
<u-popup
|
||||
:show="show"
|
||||
mode="bottom"
|
||||
closeable
|
||||
@close="close"
|
||||
:round="round"
|
||||
:closeOnClickOverlay="closeOnClickOverlay"
|
||||
>
|
||||
<view class="u-calendar">
|
||||
<uHeader
|
||||
:title="title"
|
||||
:subtitle="subtitle"
|
||||
:showSubtitle="showSubtitle"
|
||||
:showTitle="showTitle"
|
||||
></uHeader>
|
||||
<scroll-view
|
||||
:style="{
|
||||
height: $u.addUnit(listHeight)
|
||||
}"
|
||||
scroll-y
|
||||
@scroll="onScroll"
|
||||
:scroll-top="scrollTop"
|
||||
:scrollIntoView="scrollIntoView"
|
||||
>
|
||||
<uMonth
|
||||
:color="color"
|
||||
:rowHeight="rowHeight"
|
||||
:showMark="showMark"
|
||||
:months="months"
|
||||
:mode="mode"
|
||||
:maxCount="maxCount"
|
||||
:startText="startText"
|
||||
:endText="endText"
|
||||
:defaultDate="defaultDate"
|
||||
:minDate="innerMinDate"
|
||||
:maxDate="innerMaxDate"
|
||||
:maxMonth="monthNum"
|
||||
:readonly="readonly"
|
||||
:maxRange="maxRange"
|
||||
:rangePrompt="rangePrompt"
|
||||
:showRangePrompt="showRangePrompt"
|
||||
:allowSameDay="allowSameDay"
|
||||
ref="month"
|
||||
@monthSelected="monthSelected"
|
||||
@updateMonthTop="updateMonthTop"
|
||||
></uMonth>
|
||||
</scroll-view>
|
||||
<slot name="footer" v-if="showConfirm">
|
||||
<view class="u-calendar__confirm">
|
||||
<u-button
|
||||
shape="circle"
|
||||
:text="
|
||||
buttonDisabled ? confirmDisabledText : confirmText
|
||||
"
|
||||
:color="color"
|
||||
@click="confirm"
|
||||
:disabled="buttonDisabled"
|
||||
></u-button>
|
||||
</view>
|
||||
</slot>
|
||||
</view>
|
||||
</u-popup>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import uHeader from './header.vue'
|
||||
import uMonth from './month.vue'
|
||||
import props from './props.js'
|
||||
import util from './util.js'
|
||||
import dayjs from '../../libs/util/dayjs.js'
|
||||
import Calendar from '../../libs/util/calendar.js'
|
||||
/**
|
||||
* Calendar 日历
|
||||
* @description 此组件用于单个选择日期,范围选择日期等,日历被包裹在底部弹起的容器中.
|
||||
* @tutorial https://www.uviewui.com/components/calendar.html
|
||||
*
|
||||
* @property {String} title 标题内容 (默认 日期选择 )
|
||||
* @property {Boolean} showTitle 是否显示标题 (默认 true )
|
||||
* @property {Boolean} showSubtitle 是否显示副标题 (默认 true )
|
||||
* @property {String} mode 日期类型选择 single-选择单个日期,multiple-可以选择多个日期,range-选择日期范围 ( 默认 'single' )
|
||||
* @property {String} startText mode=range时,第一个日期底部的提示文字 (默认 '开始' )
|
||||
* @property {String} endText mode=range时,最后一个日期底部的提示文字 (默认 '结束' )
|
||||
* @property {Array} customList 自定义列表
|
||||
* @property {String} color 主题色,对底部按钮和选中日期有效 (默认 ‘#3c9cff' )
|
||||
* @property {String | Number} minDate 最小的可选日期 (默认 0 )
|
||||
* @property {String | Number} maxDate 最大可选日期 (默认 0 )
|
||||
* @property {Array | String| Date} defaultDate 默认选中的日期,mode为multiple或range是必须为数组格式
|
||||
* @property {String | Number} maxCount mode=multiple时,最多可选多少个日期 (默认 Number.MAX_SAFE_INTEGER )
|
||||
* @property {String | Number} rowHeight 日期行高 (默认 56 )
|
||||
* @property {Function} formatter 日期格式化函数
|
||||
* @property {Boolean} showLunar 是否显示农历 (默认 false )
|
||||
* @property {Boolean} showMark 是否显示月份背景色 (默认 true )
|
||||
* @property {String} confirmText 确定按钮的文字 (默认 '确定' )
|
||||
* @property {String} confirmDisabledText 确认按钮处于禁用状态时的文字 (默认 '确定' )
|
||||
* @property {Boolean} show 是否显示日历弹窗 (默认 false )
|
||||
* @property {Boolean} closeOnClickOverlay 是否允许点击遮罩关闭日历 (默认 false )
|
||||
* @property {Boolean} readonly 是否为只读状态,只读状态下禁止选择日期 (默认 false )
|
||||
* @property {String | Number} maxRange 日期区间最多可选天数,默认无限制,mode = range时有效
|
||||
* @property {String} rangePrompt 范围选择超过最多可选天数时的提示文案,mode = range时有效
|
||||
* @property {Boolean} showRangePrompt 范围选择超过最多可选天数时,是否展示提示文案,mode = range时有效 (默认 true )
|
||||
* @property {Boolean} allowSameDay 是否允许日期范围的起止时间为同一天,mode = range时有效 (默认 false )
|
||||
* @property {Number|String} round 圆角值,默认无圆角 (默认 0 )
|
||||
* @property {Number|String} monthNum 最多展示的月份数量 (默认 3 )
|
||||
*
|
||||
* @event {Function()} confirm 点击确定按钮时触发 选择日期相关的返回参数
|
||||
* @event {Function()} close 日历关闭时触发 可定义页面关闭时的回调事件
|
||||
* @example <u-calendar :defaultDate="defaultDateMultiple" :show="show" mode="multiple" @confirm="confirm">
|
||||
</u-calendar>
|
||||
* */
|
||||
export default {
|
||||
name: 'u-calendar',
|
||||
mixins: [uni.$u.mpMixin, uni.$u.mixin, props],
|
||||
components: {
|
||||
uHeader,
|
||||
uMonth
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 需要显示的月份的数组
|
||||
months: [],
|
||||
// 在月份滚动区域中,当前视图中月份的index索引
|
||||
monthIndex: 0,
|
||||
// 月份滚动区域的高度
|
||||
listHeight: 0,
|
||||
// month组件中选择的日期数组
|
||||
selected: [],
|
||||
scrollIntoView: '',
|
||||
scrollTop:0,
|
||||
// 过滤处理方法
|
||||
innerFormatter: (value) => value
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
selectedChange: {
|
||||
immediate: true,
|
||||
handler(n) {
|
||||
this.setMonth()
|
||||
}
|
||||
},
|
||||
// 打开弹窗时,设置月份数据
|
||||
show: {
|
||||
immediate: true,
|
||||
handler(n) {
|
||||
this.setMonth()
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 由于maxDate和minDate可以为字符串(2021-10-10),或者数值(时间戳),但是dayjs如果接受字符串形式的时间戳会有问题,这里进行处理
|
||||
innerMaxDate() {
|
||||
return uni.$u.test.number(this.maxDate)
|
||||
? Number(this.maxDate)
|
||||
: this.maxDate
|
||||
},
|
||||
innerMinDate() {
|
||||
return uni.$u.test.number(this.minDate)
|
||||
? Number(this.minDate)
|
||||
: this.minDate
|
||||
},
|
||||
// 多个条件的变化,会引起选中日期的变化,这里统一管理监听
|
||||
selectedChange() {
|
||||
return [this.innerMinDate, this.innerMaxDate, this.defaultDate]
|
||||
},
|
||||
subtitle() {
|
||||
// 初始化时,this.months为空数组,所以需要特别判断处理
|
||||
if (this.months.length) {
|
||||
return `${this.months[this.monthIndex].year}年${
|
||||
this.months[this.monthIndex].month
|
||||
}月`
|
||||
} else {
|
||||
return ''
|
||||
}
|
||||
},
|
||||
buttonDisabled() {
|
||||
// 如果为range类型,且选择的日期个数不足1个时,让底部的按钮出于disabled状态
|
||||
if (this.mode === 'range') {
|
||||
if (this.selected.length <= 1) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.start = Date.now()
|
||||
this.init()
|
||||
},
|
||||
methods: {
|
||||
// 在微信小程序中,不支持将函数当做props参数,故只能通过ref形式调用
|
||||
setFormatter(e) {
|
||||
this.innerFormatter = e
|
||||
},
|
||||
// month组件内部选择日期后,通过事件通知给父组件
|
||||
monthSelected(e) {
|
||||
this.selected = e
|
||||
if (!this.showConfirm) {
|
||||
// 在不需要确认按钮的情况下,如果为单选,或者范围多选且已选长度大于2,则直接进行返还
|
||||
if (
|
||||
this.mode === 'multiple' ||
|
||||
this.mode === 'single' ||
|
||||
(this.mode === 'range' && this.selected.length >= 2)
|
||||
) {
|
||||
this.$emit('confirm', this.selected)
|
||||
}
|
||||
}
|
||||
},
|
||||
init() {
|
||||
// 校验maxDate,不能小于当前时间
|
||||
if (
|
||||
this.innerMaxDate &&
|
||||
new Date(this.innerMaxDate).getTime() <= Date.now()
|
||||
) {
|
||||
return uni.$u.error('maxDate不能小于当前时间')
|
||||
}
|
||||
// 滚动区域的高度
|
||||
this.listHeight = this.rowHeight * 5 + 30
|
||||
this.setMonth()
|
||||
},
|
||||
close() {
|
||||
this.$emit('close')
|
||||
},
|
||||
// 点击确定按钮
|
||||
confirm() {
|
||||
if (!this.buttonDisabled) {
|
||||
this.$emit('confirm', this.selected)
|
||||
}
|
||||
},
|
||||
// 获得两个日期之间的月份数
|
||||
getMonths(minDate, maxDate) {
|
||||
const minYear = dayjs(minDate).year()
|
||||
const minMonth = dayjs(minDate).month() + 1
|
||||
const maxYear = dayjs(maxDate).year()
|
||||
const maxMonth = dayjs(maxDate).month() + 1
|
||||
return (maxYear - minYear) * 12 + (maxMonth - minMonth) + 1
|
||||
},
|
||||
// 设置月份数据
|
||||
setMonth() {
|
||||
// 最小日期的毫秒数
|
||||
const minDate = this.innerMinDate || dayjs().valueOf()
|
||||
// 如果没有指定最大日期,则往后推3个月
|
||||
const maxDate =
|
||||
this.innerMaxDate ||
|
||||
dayjs(minDate)
|
||||
.add(this.monthNum - 1, 'month')
|
||||
.valueOf()
|
||||
// 最大最小月份之间的共有多少个月份,
|
||||
const months = uni.$u.range(
|
||||
1,
|
||||
this.monthNum,
|
||||
this.getMonths(minDate, maxDate)
|
||||
)
|
||||
// 先清空数组
|
||||
this.months = []
|
||||
for (let i = 0; i < months; i++) {
|
||||
this.months.push({
|
||||
date: new Array(
|
||||
dayjs(minDate).add(i, 'month').daysInMonth()
|
||||
)
|
||||
.fill(1)
|
||||
.map((item, index) => {
|
||||
// 日期,取值1-31
|
||||
let day = index + 1
|
||||
// 星期,0-6,0为周日
|
||||
const week = dayjs(minDate)
|
||||
.add(i, 'month')
|
||||
.date(day)
|
||||
.day()
|
||||
const date = dayjs(minDate)
|
||||
.add(i, 'month')
|
||||
.date(day)
|
||||
.format('YYYY-MM-DD')
|
||||
let bottomInfo = ''
|
||||
if (this.showLunar) {
|
||||
// 将日期转为农历格式
|
||||
const lunar = Calendar.solar2lunar(
|
||||
dayjs(date).year(),
|
||||
dayjs(date).month() + 1,
|
||||
dayjs(date).date()
|
||||
)
|
||||
bottomInfo = lunar.IDayCn
|
||||
}
|
||||
let config = {
|
||||
day,
|
||||
week,
|
||||
// 小于最小允许的日期,或者大于最大的日期,则设置为disabled状态
|
||||
disabled:
|
||||
dayjs(date).isBefore(
|
||||
dayjs(minDate).format('YYYY-MM-DD')
|
||||
) ||
|
||||
dayjs(date).isAfter(
|
||||
dayjs(maxDate).format('YYYY-MM-DD')
|
||||
),
|
||||
// 返回一个日期对象,供外部的formatter获取当前日期的年月日等信息,进行加工处理
|
||||
date: new Date(date),
|
||||
bottomInfo,
|
||||
dot: false,
|
||||
month:
|
||||
dayjs(minDate).add(i, 'month').month() + 1
|
||||
}
|
||||
const formatter =
|
||||
this.formatter || this.innerFormatter
|
||||
return formatter(config)
|
||||
}),
|
||||
// 当前所属的月份
|
||||
month: dayjs(minDate).add(i, 'month').month() + 1,
|
||||
// 当前年份
|
||||
year: dayjs(minDate).add(i, 'month').year()
|
||||
})
|
||||
}
|
||||
|
||||
},
|
||||
// 滚动到默认设置的月份
|
||||
scrollIntoDefaultMonth(selected) {
|
||||
// 查询默认日期在可选列表的下标
|
||||
const _index = this.months.findIndex(({
|
||||
year,
|
||||
month
|
||||
}) => {
|
||||
month = uni.$u.padZero(month)
|
||||
return `${year}-${month}` === selected
|
||||
})
|
||||
if (_index !== -1) {
|
||||
// #ifndef MP-WEIXIN
|
||||
this.$nextTick(() => {
|
||||
this.scrollIntoView = `month-${_index}`
|
||||
})
|
||||
// #endif
|
||||
// #ifdef MP-WEIXIN
|
||||
this.scrollTop = this.months[_index].top || 0;
|
||||
// #endif
|
||||
}
|
||||
},
|
||||
// scroll-view滚动监听
|
||||
onScroll(event) {
|
||||
// 不允许小于0的滚动值,如果scroll-view到顶了,继续下拉,会出现负数值
|
||||
const scrollTop = Math.max(0, event.detail.scrollTop)
|
||||
// 将当前滚动条数值,除以滚动区域的高度,可以得出当前滚动到了哪一个月份的索引
|
||||
for (let i = 0; i < this.months.length; i++) {
|
||||
if (scrollTop >= (this.months[i].top || this.listHeight)) {
|
||||
this.monthIndex = i
|
||||
}
|
||||
}
|
||||
},
|
||||
// 更新月份的top值
|
||||
updateMonthTop(topArr = []) {
|
||||
// 设置对应月份的top值,用于onScroll方法更新月份
|
||||
topArr.map((item, index) => {
|
||||
this.months[index].top = item
|
||||
})
|
||||
|
||||
// 获取默认日期的下标
|
||||
if (!this.defaultDate) {
|
||||
// 如果没有设置默认日期,则将当天日期设置为默认选中的日期
|
||||
const selected = dayjs().format("YYYY-MM")
|
||||
this.scrollIntoDefaultMonth(selected)
|
||||
return
|
||||
}
|
||||
let selected = dayjs().format("YYYY-MM");
|
||||
// 单选模式,可以是字符串或数组,Date对象等
|
||||
if (!uni.$u.test.array(this.defaultDate)) {
|
||||
selected = dayjs(this.defaultDate).format("YYYY-MM")
|
||||
} else {
|
||||
selected = dayjs(this.defaultDate[0]).format("YYYY-MM");
|
||||
}
|
||||
this.scrollIntoDefaultMonth(selected)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../libs/css/components.scss';
|
||||
|
||||
.u-calendar {
|
||||
&__confirm {
|
||||
padding: 7px 18px;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,85 +0,0 @@
|
|||
export default {
|
||||
methods: {
|
||||
// 设置月份数据
|
||||
setMonth() {
|
||||
// 月初是周几
|
||||
const day = dayjs(this.date).date(1).day()
|
||||
const start = day == 0 ? 6 : day - 1
|
||||
|
||||
// 本月天数
|
||||
const days = dayjs(this.date).endOf('month').format('D')
|
||||
|
||||
// 上个月天数
|
||||
const prevDays = dayjs(this.date).endOf('month').subtract(1, 'month').format('D')
|
||||
|
||||
// 日期数据
|
||||
const arr = []
|
||||
// 清空表格
|
||||
this.month = []
|
||||
|
||||
// 添加上月数据
|
||||
arr.push(
|
||||
...new Array(start).fill(1).map((e, i) => {
|
||||
const day = prevDays - start + i + 1
|
||||
|
||||
return {
|
||||
value: day,
|
||||
disabled: true,
|
||||
date: dayjs(this.date).subtract(1, 'month').date(day).format('YYYY-MM-DD')
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
// 添加本月数据
|
||||
arr.push(
|
||||
...new Array(days - 0).fill(1).map((e, i) => {
|
||||
const day = i + 1
|
||||
|
||||
return {
|
||||
value: day,
|
||||
date: dayjs(this.date).date(day).format('YYYY-MM-DD')
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
// 添加下个月
|
||||
arr.push(
|
||||
...new Array(42 - days - start).fill(1).map((e, i) => {
|
||||
const day = i + 1
|
||||
|
||||
return {
|
||||
value: day,
|
||||
disabled: true,
|
||||
date: dayjs(this.date).add(1, 'month').date(day).format('YYYY-MM-DD')
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
// 分割数组
|
||||
for (let n = 0; n < arr.length; n += 7) {
|
||||
this.month.push(
|
||||
arr.slice(n, n + 7).map((e, i) => {
|
||||
e.index = i + n
|
||||
|
||||
// 自定义信息
|
||||
const custom = this.customList.find((c) => c.date == e.date)
|
||||
|
||||
// 农历
|
||||
if (this.lunar) {
|
||||
const {
|
||||
IDayCn,
|
||||
IMonthCn
|
||||
} = this.getLunar(e.date)
|
||||
e.lunar = IDayCn == '初一' ? IMonthCn : IDayCn
|
||||
}
|
||||
|
||||
return {
|
||||
...e,
|
||||
...custom
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
export default {
|
||||
props: {
|
||||
// 是否打乱键盘按键的顺序
|
||||
random: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 输入一个中文后,是否自动切换到英文
|
||||
autoChange: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,311 +0,0 @@
|
|||
<template>
|
||||
<view
|
||||
class="u-keyboard"
|
||||
@touchmove.stop.prevent="noop"
|
||||
>
|
||||
<view
|
||||
v-for="(group, i) in abc ? engKeyBoardList : areaList"
|
||||
:key="i"
|
||||
class="u-keyboard__button"
|
||||
:index="i"
|
||||
:class="[i + 1 === 4 && 'u-keyboard__button--center']"
|
||||
>
|
||||
<view
|
||||
v-if="i === 3"
|
||||
class="u-keyboard__button__inner-wrapper"
|
||||
>
|
||||
<view
|
||||
class="u-keyboard__button__inner-wrapper__left"
|
||||
hover-class="u-hover-class"
|
||||
:hover-stay-time="200"
|
||||
@tap="changeCarInputMode"
|
||||
>
|
||||
<text
|
||||
class="u-keyboard__button__inner-wrapper__left__lang"
|
||||
:class="[!abc && 'u-keyboard__button__inner-wrapper__left__lang--active']"
|
||||
>中</text>
|
||||
<text class="u-keyboard__button__inner-wrapper__left__line">/</text>
|
||||
<text
|
||||
class="u-keyboard__button__inner-wrapper__left__lang"
|
||||
:class="[abc && 'u-keyboard__button__inner-wrapper__left__lang--active']"
|
||||
>英</text>
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
class="u-keyboard__button__inner-wrapper"
|
||||
v-for="(item, j) in group"
|
||||
:key="j"
|
||||
>
|
||||
<view
|
||||
class="u-keyboard__button__inner-wrapper__inner"
|
||||
:hover-stay-time="200"
|
||||
@tap="carInputClick(i, j)"
|
||||
hover-class="u-hover-class"
|
||||
>
|
||||
<text class="u-keyboard__button__inner-wrapper__inner__text">{{ item }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
v-if="i === 3"
|
||||
@touchstart="backspaceClick"
|
||||
@touchend="clearTimer"
|
||||
class="u-keyboard__button__inner-wrapper"
|
||||
>
|
||||
<view
|
||||
class="u-keyboard__button__inner-wrapper__right"
|
||||
hover-class="u-hover-class"
|
||||
:hover-stay-time="200"
|
||||
>
|
||||
<u-icon
|
||||
size="28"
|
||||
name="backspace"
|
||||
color="#303133"
|
||||
></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import props from './props.js';
|
||||
/**
|
||||
* keyboard 键盘组件
|
||||
* @description 此为uView自定义的键盘面板,内含了数字键盘,车牌号键,身份证号键盘3种模式,都有可以打乱按键顺序的选项。
|
||||
* @tutorial https://uviewui.com/components/keyboard.html
|
||||
* @property {Boolean} random 是否打乱键盘的顺序
|
||||
* @event {Function} change 点击键盘触发
|
||||
* @event {Function} backspace 点击退格键触发
|
||||
* @example <u-keyboard ref="uKeyboard" mode="car" v-model="show"></u-keyboard>
|
||||
*/
|
||||
export default {
|
||||
name: "u-keyboard",
|
||||
mixins: [uni.$u.mpMixin, uni.$u.mixin, props],
|
||||
data() {
|
||||
return {
|
||||
// 车牌输入时,abc=true为输入车牌号码,bac=false为输入省份中文简称
|
||||
abc: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
areaList() {
|
||||
let data = [
|
||||
'京',
|
||||
'沪',
|
||||
'粤',
|
||||
'津',
|
||||
'冀',
|
||||
'豫',
|
||||
'云',
|
||||
'辽',
|
||||
'黑',
|
||||
'湘',
|
||||
'皖',
|
||||
'鲁',
|
||||
'苏',
|
||||
'浙',
|
||||
'赣',
|
||||
'鄂',
|
||||
'桂',
|
||||
'甘',
|
||||
'晋',
|
||||
'陕',
|
||||
'蒙',
|
||||
'吉',
|
||||
'闽',
|
||||
'贵',
|
||||
'渝',
|
||||
'川',
|
||||
'青',
|
||||
'琼',
|
||||
'宁',
|
||||
'挂',
|
||||
'藏',
|
||||
'港',
|
||||
'澳',
|
||||
'新',
|
||||
'使',
|
||||
'学'
|
||||
];
|
||||
let tmp = [];
|
||||
// 打乱顺序
|
||||
if (this.random) data = uni.$u.randomArray(data);
|
||||
// 切割成二维数组
|
||||
tmp[0] = data.slice(0, 10);
|
||||
tmp[1] = data.slice(10, 20);
|
||||
tmp[2] = data.slice(20, 30);
|
||||
tmp[3] = data.slice(30, 36);
|
||||
return tmp;
|
||||
},
|
||||
engKeyBoardList() {
|
||||
let data = [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
0,
|
||||
'Q',
|
||||
'W',
|
||||
'E',
|
||||
'R',
|
||||
'T',
|
||||
'Y',
|
||||
'U',
|
||||
'I',
|
||||
'O',
|
||||
'P',
|
||||
'A',
|
||||
'S',
|
||||
'D',
|
||||
'F',
|
||||
'G',
|
||||
'H',
|
||||
'J',
|
||||
'K',
|
||||
'L',
|
||||
'Z',
|
||||
'X',
|
||||
'C',
|
||||
'V',
|
||||
'B',
|
||||
'N',
|
||||
'M'
|
||||
];
|
||||
let tmp = [];
|
||||
if (this.random) data = uni.$u.randomArray(data);
|
||||
tmp[0] = data.slice(0, 10);
|
||||
tmp[1] = data.slice(10, 20);
|
||||
tmp[2] = data.slice(20, 30);
|
||||
tmp[3] = data.slice(30, 36);
|
||||
return tmp;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 点击键盘按钮
|
||||
carInputClick(i, j) {
|
||||
let value = '';
|
||||
// 不同模式,获取不同数组的值
|
||||
if (this.abc) value = this.engKeyBoardList[i][j];
|
||||
else value = this.areaList[i][j];
|
||||
// 如果允许自动切换,则将中文状态切换为英文
|
||||
if (!this.abc && this.autoChange) uni.$u.sleep(200).then(() => this.abc = true)
|
||||
this.$emit('change', value);
|
||||
},
|
||||
// 修改汽车牌键盘的输入模式,中文|英文
|
||||
changeCarInputMode() {
|
||||
this.abc = !this.abc;
|
||||
},
|
||||
// 点击退格键
|
||||
backspaceClick() {
|
||||
this.$emit('backspace');
|
||||
clearInterval(this.timer); //再次清空定时器,防止重复注册定时器
|
||||
this.timer = null;
|
||||
this.timer = setInterval(() => {
|
||||
this.$emit('backspace');
|
||||
}, 250);
|
||||
},
|
||||
clearTimer() {
|
||||
clearInterval(this.timer);
|
||||
this.timer = null;
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/components.scss";
|
||||
$u-car-keyboard-background-color: rgb(224, 228, 230) !default;
|
||||
$u-car-keyboard-padding:6px 0 6px !default;
|
||||
$u-car-keyboard-button-inner-width:64rpx !default;
|
||||
$u-car-keyboard-button-inner-background-color:#FFFFFF !default;
|
||||
$u-car-keyboard-button-height:80rpx !default;
|
||||
$u-car-keyboard-button-inner-box-shadow:0 1px 0px #999992 !default;
|
||||
$u-car-keyboard-button-border-radius:4px !default;
|
||||
$u-car-keyboard-button-inner-margin:8rpx 5rpx !default;
|
||||
$u-car-keyboard-button-text-font-size:16px !default;
|
||||
$u-car-keyboard-button-text-color:$u-main-color !default;
|
||||
$u-car-keyboard-center-inner-margin: 0 4rpx !default;
|
||||
$u-car-keyboard-special-button-width:134rpx !default;
|
||||
$u-car-keyboard-lang-font-size:16px !default;
|
||||
$u-car-keyboard-lang-color:$u-main-color !default;
|
||||
$u-car-keyboard-active-color:$u-primary !default;
|
||||
$u-car-keyboard-line-font-size:15px !default;
|
||||
$u-car-keyboard-line-color:$u-main-color !default;
|
||||
$u-car-keyboard-line-margin:0 1px !default;
|
||||
$u-car-keyboard-u-hover-class-background-color:#BBBCC6 !default;
|
||||
|
||||
.u-keyboard {
|
||||
@include flex(column);
|
||||
justify-content: space-around;
|
||||
background-color: $u-car-keyboard-background-color;
|
||||
align-items: stretch;
|
||||
padding: $u-car-keyboard-padding;
|
||||
|
||||
&__button {
|
||||
@include flex;
|
||||
justify-content: center;
|
||||
flex: 1;
|
||||
/* #ifndef APP-NVUE */
|
||||
/* #endif */
|
||||
|
||||
&__inner-wrapper {
|
||||
box-shadow: $u-car-keyboard-button-inner-box-shadow;
|
||||
margin: $u-car-keyboard-button-inner-margin;
|
||||
border-radius: $u-car-keyboard-button-border-radius;
|
||||
|
||||
&__inner {
|
||||
@include flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: $u-car-keyboard-button-inner-width;
|
||||
background-color: $u-car-keyboard-button-inner-background-color;
|
||||
height: $u-car-keyboard-button-height;
|
||||
border-radius: $u-car-keyboard-button-border-radius;
|
||||
|
||||
&__text {
|
||||
font-size: $u-car-keyboard-button-text-font-size;
|
||||
color: $u-car-keyboard-button-text-color;
|
||||
}
|
||||
}
|
||||
|
||||
&__left,
|
||||
&__right {
|
||||
border-radius: $u-car-keyboard-button-border-radius;
|
||||
width: $u-car-keyboard-special-button-width;
|
||||
height: $u-car-keyboard-button-height;
|
||||
background-color: $u-car-keyboard-u-hover-class-background-color;
|
||||
@include flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
box-shadow: $u-car-keyboard-button-inner-box-shadow;
|
||||
}
|
||||
|
||||
&__left {
|
||||
&__line {
|
||||
font-size: $u-car-keyboard-line-font-size;
|
||||
color: $u-car-keyboard-line-color;
|
||||
margin: $u-car-keyboard-line-margin;
|
||||
}
|
||||
|
||||
&__lang {
|
||||
font-size: $u-car-keyboard-lang-font-size;
|
||||
color: $u-car-keyboard-lang-color;
|
||||
|
||||
&--active {
|
||||
color: $u-car-keyboard-active-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.u-hover-class {
|
||||
background-color: $u-car-keyboard-u-hover-class-background-color;
|
||||
}
|
||||
</style>
|
|
@ -1,14 +0,0 @@
|
|||
export default {
|
||||
props: {
|
||||
// 分组标题
|
||||
title: {
|
||||
type: String,
|
||||
default: uni.$u.props.cellGroup.title
|
||||
},
|
||||
// 是否显示外边框
|
||||
border: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.cellGroup.border
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
<template>
|
||||
<view :style="[$u.addStyle(customStyle)]" :class="[customClass]" class="u-cell-group">
|
||||
<view v-if="title" class="u-cell-group__title">
|
||||
<slot name="title">
|
||||
<text class="u-cell-group__title__text">{{ title }}</text>
|
||||
</slot>
|
||||
</view>
|
||||
<view class="u-cell-group__wrapper">
|
||||
<u-line v-if="border"></u-line>
|
||||
<slot />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import props from './props.js';
|
||||
/**
|
||||
* cellGroup 单元格
|
||||
* @description cell单元格一般用于一组列表的情况,比如个人中心页,设置页等。
|
||||
* @tutorial https://uviewui.com/components/cell.html
|
||||
*
|
||||
* @property {String} title 分组标题
|
||||
* @property {Boolean} border 是否显示外边框 (默认 true )
|
||||
* @property {Object} customStyle 定义需要用到的外部样式
|
||||
*
|
||||
* @event {Function} click 点击cell列表时触发
|
||||
* @example <u-cell-group title="设置喜好">
|
||||
*/
|
||||
export default {
|
||||
name: 'u-cell-group',
|
||||
mixins: [uni.$u.mpMixin, uni.$u.mixin,props],
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/components.scss";
|
||||
|
||||
$u-cell-group-title-padding: 16px 16px 8px !default;
|
||||
$u-cell-group-title-font-size: 15px !default;
|
||||
$u-cell-group-title-line-height: 16px !default;
|
||||
$u-cell-group-title-color: $u-main-color !default;
|
||||
|
||||
.u-cell-group {
|
||||
flex: 1;
|
||||
|
||||
&__title {
|
||||
padding: $u-cell-group-title-padding;
|
||||
|
||||
&__text {
|
||||
font-size: $u-cell-group-title-font-size;
|
||||
line-height: $u-cell-group-title-line-height;
|
||||
color: $u-cell-group-title-color;
|
||||
}
|
||||
}
|
||||
|
||||
&__wrapper {
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|